Repository: Flexget/Flexget

Issue:
discover crash
```
2015-12-06 11:21 CRITICAL task          tv-shows-find   BUG: Unhandled error in plugin discover: 'NoneType' object has no attribute 'read'
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flexget/task.py", line 438, in __run_plugin
    return method(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/event.py", line 22, in __call__
    return self.func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/discover.py", line 268, in on_task_input
    entries = self.estimated(entries)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/discover.py", line 190, in estimated
    est_date = estimator.estimate(entry)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/estimators/est_released.py", line 28, in estimate
    estimate = estimator(entry)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/estimators/est_release_series_tvmaze.py", line 52, in estimate
    episode = lookup(**kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/utils/database.py", line 30, in wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 397, in episode_lookup
    episode = TVMazeEpisodes(pytvmaze_episode, series.tvmaze_id)
  File "<string>", line 4, in __init__
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/state.py", line 306, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/state.py", line 303, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 159, in __init__
    self.update(episode)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 176, in update
    self.airstamp = parser.parse(episode.airstamp)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 1008, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 392, in parse
    res = self._parse(timestr, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 492, in _parse
    l = _timelex.split(timestr)         # Splits the timestr into tokens
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 174, in split
    return list(cls(s))
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 171, in next
    return self.__next__()  # Python 2.x support
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 164, in __next__
    token = self.get_token()
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 82, in get_token
    nextchar = self.instream.read(1)
AttributeError: 'NoneType' object has no attribute 'read'
```

Let me know if you need any more information!



Discussion:
Thinking this is probably fixed with SHA: 4c0498b4b5d5d265a8ae845f7f620f349058b3fd

Actually with https://github.com/Flexget/Flexget/commit/527d5fc6c3cb7b4f5623175f96eff45acb90d33b
Should be fixed with next release. 
In the mean time you can disable estimators. Look in discover wiki for details. 

This should be fixed now, please try again and report back.

Tried with 1.2.402 and got the same error:

```
2015-12-07 20:40 CRITICAL task          tv-shows-find   BUG: Unhandled error in plugin discover: 'NoneType' object has no attribute 'read'
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flexget/task.py", line 438, in __run_plugin
    return method(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/event.py", line 22, in __call__
    return self.func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/discover.py", line 268, in on_task_input
    entries = self.estimated(entries)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/input/discover.py", line 190, in estimated
    est_date = estimator.estimate(entry)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/estimators/est_released.py", line 28, in estimate
    estimate = estimator(entry)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/estimators/est_release_series_tvmaze.py", line 52, in estimate
    episode = lookup(**kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/utils/database.py", line 30, in wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 406, in episode_lookup
    episode = TVMazeEpisodes(pytvmaze_episode, series.tvmaze_id)
  File "<string>", line 4, in __init__
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/state.py", line 306, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/orm/state.py", line 303, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 159, in __init__
    self.update(episode)
  File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tvmaze.py", line 176, in update
    self.airstamp = parser.parse(episode.airstamp)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 1008, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 392, in parse
    res = self._parse(timestr, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 492, in _parse
    l = _timelex.split(timestr)         # Splits the timestr into tokens
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 174, in split
    return list(cls(s))
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 171, in next
    return self.__next__()  # Python 2.x support
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 164, in __next__
    token = self.get_token()
  File "/usr/local/lib/python2.7/dist-packages/dateutil/parser.py", line 82, in get_token
    nextchar = self.instream.read(1)
AttributeError: 'NoneType' object has no attribute 'read'
```

Luckily the workaround works!

@liiight check to to make sure variables are there before parsing. especially air_dates because it might not have aired yet. https://github.com/Flexget/Flexget/blob/develop/flexget/plugins/api_tvmaze.py#L176

Right, my bad. I'll push a fix asap


Resolving patch:
diff --git a/flexget/plugins/api_tvmaze.py b/flexget/plugins/api_tvmaze.py
--- a/flexget/plugins/api_tvmaze.py
+++ b/flexget/plugins/api_tvmaze.py
@@ -84,41 +84,37 @@ def update(self, series, session):
         self.language = series.language
         self.schedule = series.schedule
         self.url = series.url
-        try:
+        if series.image:
             self.original_image = series.image.get('original')
-        except AttributeError:
-            self.original_image = None
-        try:
             self.medium_image = series.image.get('medium')
-        except AttributeError:
+        else:
+            self.original_image = None
             self.medium_image = None
         self.tvdb_id = series.externals.get('thetvdb')
         self.tvrage_id = series.externals.get('tvrage')
-        try:
+        if series.premiered:
             self.premiered = parser.parse(series.premiered)
-        except AttributeError:
+            self.year = int(series.premiered[:4])
+        else:
             self.premiered = None
+            self.year = None
         self.summary = series.summary
-        try:
+        if series.webChannel:
             self.webchannel = series.webChannel.get('name')
-        except AttributeError:
+        else:
             self.webchannel = None
         self.runtime = series.runtime
         self.show_type = series.type
-        try:
+        if series.network:
             self.network = series.network.get('name')
-        except AttributeError:
+        else:
             self.network = None
-        try:
-            self.year = int(series.premiered[:4])
-        except (TypeError, ValueError):
-            self.year = None
         self.last_update = datetime.now()
 
         self.genres[:] = get_db_genres(series.genres, session)
 
     def __repr__(self):
-        return '<TVMazeSeries(title=%s,id=%s,last_update=%s)>' % (self.name, self.id, self.last_update)
+        return '<TVMazeSeries(title=%s,id=%s,last_update=%s)>' % (self.name, self.tvmaze_id, self.last_update)
 
     def __str__(self):
         return self.name
@@ -160,20 +156,21 @@ def __init__(self, episode, series_id):
 
     def update(self, episode):
         self.title = episode.title
-        try:
+        if episode.airdate:
             self.airdate = datetime.strptime(episode.airdate, '%Y-%m-%d')
-        except ValueError:
+        else:
             self.airdate = None
         self.url = episode.url
-        try:
+        if episode.image:
             self.original_image = episode.image.get('original')
-        except AttributeError:
-            self.original_image = None
-        try:
             self.medium_image = episode.image.get('medium')
-        except AttributeError:
+        else:
+            self.original_image = None
             self.medium_image = None
-        self.airstamp = parser.parse(episode.airstamp)
+        if episode.airstamp:
+            self.airstamp = parser.parse(episode.airstamp)
+        else:
+            self.airstamp = None
         self.runtime = episode.runtime
         self.last_update = datetime.now()
 


Test patch:
diff --git a/tests/cassettes/test_tvmaze.TestTVMazeShowLookup.test_episode_without_air_date_and_air_stamp b/tests/cassettes/test_tvmaze.TestTVMazeShowLookup.test_episode_without_air_date_and_air_stamp
new file mode 100644
--- /dev/null
+++ b/tests/cassettes/test_tvmaze.TestTVMazeShowLookup.test_episode_without_air_date_and_air_stamp
@@ -0,0 +1,55 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Connection: [close]
+      Host: [api.tvmaze.com]
+      User-Agent: [Python-urllib/2.7]
+    method: GET
+    uri: http://api.tvmaze.com/search/shows?q=Chicago%20Fire
+  response:
+    body: {string: !!python/unicode '[{"score":2.3654017,"show":{"id":59,"url":"http://www.tvmaze.com/shows/59/chicago-fire","name":"Chicago
+        Fire","type":"Scripted","language":"English","genres":["Drama","Action","Adventure"],"status":"Running","runtime":60,"premiered":"2012-10-10","schedule":{"time":"22:00","days":["Tuesday"]},"rating":{"average":8.9},"weight":19,"network":{"id":1,"name":"NBC","country":{"name":"United
+        States","code":"US","timezone":"America/New_York"}},"webChannel":null,"externals":{"tvrage":30748,"thetvdb":258541},"image":{"medium":"http://tvmazecdn.com/uploads/images/medium_portrait/17/43868.jpg","original":"http://tvmazecdn.com/uploads/images/original_untouched/17/43868.jpg"},"summary":"<p><em>\"No
+        job is more stressful, dangerous or exhilarating than those of the Firefighters,
+        Rescue Squad and Paramedics of Chicago Firehouse 51. These are the courageous
+        men and women who forge headfirst into danger when everyone else is running
+        the other way and whose actions make the difference between life and death.
+        These are their stories.\"</em></p><p>From renowned Emmy Award-winning executive
+        producer Dick Wolf and creators Derek Haas and Michael Brandt comes season
+        four of the high-octane drama \"Chicago Fire\" - an edge-of-your-seat view
+        into the lives of everyday heroes committed to one of America''s noblest professions.
+        For the firefighters, rescue squad and paramedics of Chicago Firehouse 51,
+        no occupation is more stressful or dangerous, yet so rewarding and exhilarating.
+        These courageous men and women are among the elite who forge headfirst into
+        danger when everyone else is running the other way and whose actions make
+        the difference between life and death.</p><p>The pressure to perform on such
+        a high level has a way of taking a personal toll, sometimes putting team members
+        from the Truck and the specially trained Rescue Squad at odds with each other.
+        Despite any differences, this is an extended family, and when it''s \"go time,\"
+        everyone inside Firehouse 51 knows no other way than to lay it all on the
+        line for each other. Lt. Matthew Casey (Jesse Spencer) leads the Truck Company,
+        and brash Lt. Kelly Severide (Taylor Kinney) runs the Rescue Squad.</p><p>The
+        firehouse also includes Battalion Chief Wallace Boden (Eamonn Walker), a fireman''s
+        fireman who recently welcomed a newborn son with his wife, Donna (Melissa
+        Ponzio). As chief of the firehouse, it''s Boden''s job to keep everything
+        in line and running smoothly between truck and squad. Paramedic turned tough-as-nails
+        firefighter Gabriela Dawson (Monica Raymund) returns alongside Paramedic Sylvie
+        Brett (Kara Killmer) and seasoned veteran Christopher Herrmann (David Eigenberg).
+        Dora Madison also joins the cast as spunky, spirited paramedic Jessica \"Chili\"
+        Chilton.</p><p>Also returning for the fourth season are Randy \"Mouch\" McHolland
+        (Christian Stolte), Jose Cruz (Joe Minoso) and Brian \"Otis\" Zvonecek (Yuri
+        Sardarov).</p><p>In addition to Wolf, Haas and Brandt, executive producers
+        also include Matt Olmstead, Joe Chappelle, Danielle Gelber, Arthur Forney
+        and Peter Jankowski.</p><p>\"Chicago Fire\" is produced by Universal Television
+        and Wolf Films.</p>","updated":1449563509,"_links":{"self":{"href":"http://api.tvmaze.com/shows/59"},"previousepisode":{"href":"http://api.tvmaze.com/episodes/432941"},"nextepisode":{"href":"http://api.tvmaze.com/episodes/432942"}}}}]'}
+    headers:
+      access-control-allow-origin: ['*']
+      cache-control: ['public, max-age=3600']
+      connection: [close]
+      content-type: [application/json; charset=UTF-8]
+      date: ['Tue, 08 Dec 2015 15:24:20 GMT']
+      server: [nginx/1.4.6 (Ubuntu)]
+      x-powered-by: [PHP/5.5.9-1ubuntu4.11]
+    status: {code: 200, message: OK}
+version: 1
diff --git a/tests/test_tvmaze.py b/tests/test_tvmaze.py
--- a/tests/test_tvmaze.py
+++ b/tests/test_tvmaze.py
@@ -2,6 +2,9 @@
 
 from datetime import timedelta, datetime
 
+import mock
+import pytvmaze
+
 from flexget.manager import Session
 from flexget.plugins.api_tvmaze import APITVMaze, TVMazeLookup, TVMazeSeries
 from tests import FlexGetBase, use_vcr
@@ -14,7 +17,6 @@ class TestTVMazeShowLookup(FlexGetBase):
         templates:
           global:
             tvmaze_lookup: yes
-            # Access a tvdb field to cause lazy loading to occur
             set:
               afield: "{{tvdb_id}}{{tvmaze_episode_name}}{{tvmaze_series_name}}"
         tasks:
@@ -78,11 +80,24 @@ class TestTVMazeShowLookup(FlexGetBase):
               - Jake 2.0
               - Unwrapped 2.0
               - Tosh.0
+          test_episode_without_air_date:
+            mock:
+              - {title: 'Chicago Fire S04E08 HDTV x264-LOL'}
+            series:
+              - Chicago Fire
+            set:
+              bfield: "{{tvmaze_episode_airdate}}{{tvmaze_episode_airstamp}}"
+
     """
+    episode = pytvmaze.episode_by_number(maze_id=59, season_number=4, episode_number=8)
+    assert episode.airdate == '2015-12-01', 'Expected airdate is 2015-12-01, got %s' % episode.airdate
+    assert episode.airstamp == '2015-12-01T22:00:00-05:00', \
+        'Expected airstamp is 2015-12-01T22:00:00-05:00, got %s' % episode.airstamp
+    episode.airdate = None
+    episode.airstamp = None
 
     @use_vcr
     def test_lookup_name(self):
-        """tvmaze: Test Lookup (ONLINE)"""
         self.execute_task('test')
         entry = self.task.find_entry(title='House.S01E02.HDTV.XViD-FlexGet')
         assert entry['tvmaze_series_id'] == 118, \
@@ -92,7 +107,6 @@ def test_lookup_name(self):
 
     @use_vcr
     def test_lookup(self):
-        """tvmaze: Test Lookup (ONLINE)"""
         self.execute_task('test')
         entry = self.task.find_entry(title='House.S01E02.HDTV.XViD-FlexGet')
         assert entry['tvmaze_episode_name'] == 'Paternity', \
@@ -260,9 +274,21 @@ def test_show_contain_number(self):
         assert entry['tvmaze_episode_id'] == 387214, 'episode id should be 387214, instead its %s' % entry[
             'tvmaze_episode_id']
         entry = self.task.find_entry(series_name='Jake 2.0')
-        assert entry['tvmaze_series_name'] == 'Jake 2.0',  \
+        assert entry['tvmaze_series_name'] == 'Jake 2.0', \
             'tvmaze_series_name should be Jake 2.0, instead its %s' % entry['tvmaze_series_name']
         assert entry['tvmaze_series_id'] == 2381, 'series id should be 2381, instead its %s' % entry[
             'tvmaze_series_id']
         assert entry['tvmaze_episode_id'] == 184265, 'episode id should be 184265, instead its %s' % entry[
             'tvmaze_episode_id']
+
+    @use_vcr()
+    @mock.patch('flexget.plugins.api_tvmaze.episode_by_number', autospec=True)
+    def test_episode_without_air_date_and_air_stamp(self, episode_mock):
+        episode_mock.return_value = self.episode
+        self.execute_task('test_episode_without_air_date')
+
+        entry = self.task.find_entry(title='Chicago Fire S04E08 HDTV x264-LOL')
+        assert entry['tvmaze_episode_airdate'] == None, \
+            'Expected airdate to be None, got %s' % entry['tvmaze_episode_airdate']
+        assert entry['tvmaze_episode_airstamp'] == None, \
+            'Expected airdate to be None, got %s' % entry['tvmaze_episode_airstamp']