1from datetime import datetime, timedelta, date 2 3from AnyQt.QtCore import Qt 4from AnyQt.QtWidgets import QApplication, QFormLayout 5 6from Orange.data import StringVariable 7from Orange.widgets.credentials import CredentialManager 8from Orange.widgets.settings import Setting 9from Orange.widgets.widget import OWWidget, Msg, Output 10from orangecontrib.text.corpus import Corpus 11from orangecontrib.text.nyt import NYT, MIN_DATE 12from orangecontrib.text.widgets.utils import CheckListLayout, DatePickerInterval, QueryBox, \ 13 gui_require, asynchronous 14 15try: 16 from orangewidget import gui 17except ImportError: 18 from Orange.widgets import gui 19 20 21class OWNYT(OWWidget): 22 class APICredentialsDialog(OWWidget): 23 name = "New York Times API key" 24 want_main_area = False 25 resizing_enabled = False 26 cm_key = CredentialManager('NY Times API Key') 27 key_input = '' 28 29 class Error(OWWidget.Error): 30 invalid_credentials = Msg('This credentials are invalid. ' 31 'Check the key and your internet connection.') 32 33 def __init__(self, parent): 34 super().__init__() 35 self.parent = parent 36 self.api = None 37 38 form = QFormLayout() 39 form.setContentsMargins(5, 5, 5, 5) 40 self.key_edit = gui.lineEdit(self, self, 'key_input', controlWidth=400) 41 form.addRow('Key:', self.key_edit) 42 self.controlArea.layout().addLayout(form) 43 self.submit_button = gui.button(self.controlArea, self, "OK", self.accept) 44 45 self.load_credentials() 46 47 def load_credentials(self): 48 self.key_edit.setText(self.cm_key.key) 49 50 def save_credentials(self): 51 self.cm_key.key = self.key_input 52 53 def check_credentials(self): 54 api = NYT(self.key_input) 55 if api.api_key_valid(): 56 self.save_credentials() 57 else: 58 api = None 59 self.api = api 60 61 def accept(self, silent=False): 62 if not silent: self.Error.invalid_credentials.clear() 63 self.check_credentials() 64 if self.api: 65 self.parent.update_api(self.api) 66 super().accept() 67 elif not silent: 68 self.Error.invalid_credentials() 69 70 name = "NY Times" 71 description = "Fetch articles from the New York Times search API." 72 icon = "icons/NYTimes.svg" 73 priority = 130 74 75 class Outputs: 76 corpus = Output("Corpus", Corpus) 77 78 want_main_area = False 79 resizing_enabled = False 80 81 recent_queries = Setting([]) 82 date_from = Setting((datetime.now().date() - timedelta(365))) 83 date_to = Setting(datetime.now().date()) 84 85 attributes = [feat.name for feat, _ in NYT.metas if isinstance(feat, StringVariable)] 86 text_includes = Setting([feat.name for feat in NYT.text_features]) 87 88 class Warning(OWWidget.Warning): 89 no_text_fields = Msg('Text features are inferred when none are selected.') 90 91 class Error(OWWidget.Error): 92 no_api = Msg('Please provide a valid API key.') 93 no_query = Msg('Please provide a query.') 94 offline = Msg('No internet connection.') 95 api_error = Msg('API error: {}') 96 rate_limit = Msg('Rate limit exceeded. Please try again later.') 97 98 def __init__(self): 99 super().__init__() 100 self.corpus = None 101 self.nyt_api = None 102 self.output_info = '' 103 self.num_retrieved = 0 104 self.num_all = 0 105 106 # API key 107 self.api_dlg = self.APICredentialsDialog(self) 108 self.api_dlg.accept(silent=True) 109 gui.button(self.controlArea, self, 'Article API Key', callback=self.api_dlg.exec_, 110 focusPolicy=Qt.NoFocus) 111 112 # Query 113 query_box = gui.widgetBox(self.controlArea, 'Query', addSpace=True) 114 self.query_box = QueryBox(query_box, self, self.recent_queries, 115 callback=self.new_query_input) 116 117 # Year box 118 date_box = gui.hBox(query_box) 119 DatePickerInterval(date_box, self, 'date_from', 'date_to', 120 min_date=MIN_DATE, max_date=date.today(), 121 margin=(0, 3, 0, 0)) 122 123 # Text includes features 124 self.controlArea.layout().addWidget( 125 CheckListLayout('Text includes', self, 'text_includes', self.attributes, 126 cols=2, callback=self.set_text_features)) 127 128 # Output 129 info_box = gui.hBox(self.controlArea, 'Output') 130 gui.label(info_box, self, 'Articles: %(output_info)s') 131 132 # Buttons 133 self.button_box = gui.hBox(self.controlArea) 134 135 self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop, 136 focusPolicy=Qt.NoFocus) 137 138 def new_query_input(self): 139 self.search.stop() 140 self.run_search() 141 142 def start_stop(self): 143 if self.search.running: 144 self.search.stop() 145 else: 146 self.query_box.synchronize(silent=True) 147 self.run_search() 148 149 @gui_require('nyt_api', 'no_api') 150 @gui_require('recent_queries', 'no_query') 151 def run_search(self): 152 self.search() 153 154 @asynchronous 155 def search(self): 156 return self.nyt_api.search(self.recent_queries[0], self.date_from, self.date_to, 157 on_progress=self.progress_with_info, 158 should_break=self.search.should_break) 159 160 @search.callback(should_raise=False) 161 def progress_with_info(self, n_retrieved, n_all): 162 self.progressBarSet(100 * (n_retrieved / n_all if n_all else 1)) # prevent division by 0 163 self.num_all = n_all 164 self.num_retrieved = n_retrieved 165 self.update_info_label() 166 167 @search.on_start 168 def on_start(self): 169 self.Error.api_error.clear() 170 self.Error.rate_limit.clear() 171 self.Error.offline.clear() 172 self.num_all, self.num_retrieved = 0, 0 173 self.update_info_label() 174 self.progressBarInit() 175 self.search_button.setText('Stop') 176 self.Outputs.corpus.send(None) 177 178 @search.on_result 179 def on_result(self, result): 180 self.search_button.setText('Search') 181 self.corpus = result 182 self.set_text_features() 183 self.progressBarFinished() 184 185 def update_info_label(self): 186 self.output_info = '{}/{}'.format(self.num_retrieved, self.num_all) 187 188 def set_text_features(self): 189 self.Warning.no_text_fields.clear() 190 if not self.text_includes: 191 self.Warning.no_text_fields() 192 193 if self.corpus is not None: 194 vars_ = [var for var in self.corpus.domain.metas if var.name in self.text_includes] 195 self.corpus.set_text_features(vars_ or None) 196 self.Outputs.corpus.send(self.corpus) 197 198 def update_api(self, api): 199 self.nyt_api = api 200 self.Error.no_api.clear() 201 self.nyt_api.on_error = self.Error.api_error 202 self.nyt_api.on_rate_limit = self.Error.rate_limit 203 self.nyt_api.on_no_connection = self.Error.offline 204 205 def send_report(self): 206 self.report_items([ 207 ('Query', self.recent_queries[0] if self.recent_queries else ''), 208 ('Date from', self.date_from), 209 ('Date to', self.date_to), 210 ('Text includes', ', '.join(self.text_includes)), 211 ('Output', self.output_info or 'Nothing'), 212 ]) 213 214 215if __name__ == '__main__': 216 app = QApplication([]) 217 widget = OWNYT() 218 widget.show() 219 app.exec() 220