def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.api = WikipediaAPI(on_error=self.Error.api_error) self.result = None query_box = gui.hBox(self.controlArea, 'Query') # Queries configuration layout = QGridLayout() layout.setSpacing(7) row = 0 self.query_edit = ListEdit(self, 'query_list', "Each line represents a " "separate query.", 100, self) layout.addWidget(QLabel('Query word list:'), row, 0, 1, self.label_width) layout.addWidget(self.query_edit, row, self.label_width, 1, self.widgets_width) # Language row += 1 language_edit = ComboBox(self, 'language', tuple(sorted(lang2code.items()))) layout.addWidget(QLabel('Language:'), row, 0, 1, self.label_width) layout.addWidget(language_edit, row, self.label_width, 1, self.widgets_width) # Articles per query row += 1 layout.addWidget(QLabel('Articles per query:'), row, 0, 1, self.label_width) slider = gui.valueSlider(query_box, self, 'articles_per_query', box='', values=[1, 3, 5, 10, 25]) layout.addWidget(slider.box, row, 1, 1, self.widgets_width) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget(query_box) self.controlArea.layout().addWidget( CheckListLayout('Text includes', self, 'text_includes', self.attributes, cols=2, callback=self.set_text_features)) self.info_box = gui.hBox(self.controlArea, 'Info') self.result_label = gui.label(self.info_box, self, self.info_label.format(0)) self.button_box = gui.hBox(self.controlArea) self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop) self.search_button.setFocusPolicy(Qt.NoFocus)
class OWTwitter(OWWidget, ConcurrentWidgetMixin): name = "Twitter" description = "Load tweets from the Twitter API." icon = "icons/Twitter.svg" keywords = ["twitter", "tweet"] priority = 150 class Outputs: corpus = Output("Corpus", Corpus) want_main_area = False resizing_enabled = False class Info(OWWidget.Information): nut_enough_tweets = Msg( "Downloaded fewer tweets than requested, since not enough tweets or rate limit reached" ) class Error(OWWidget.Error): api_error = Msg("Api error: {}") empty_query = Msg("Please provide {}.") key_missing = Msg("Please provide a valid API token.") wrong_author = Msg("Author '{}' does not exist.") CONTENT, AUTHOR = 0, 1 MODES = ["Content", "Author"] word_list: List = Setting([]) mode: int = Setting(0) limited_search: bool = Setting(True) max_tweets: int = Setting(100) language: Optional[str] = Setting(None) allow_retweets: bool = Setting(False) collecting: bool = Setting(False) class APICredentialsDialog(OWWidget): name = "Twitter API Credentials" want_main_area = False resizing_enabled = False def __init__(self, parent): super().__init__() self.cm_key = CredentialManager("Twitter Bearer Token") self.parent = parent box = gui.vBox(self.controlArea, "Bearer Token") self.key_edit = QPlainTextEdit() box.layout().addWidget(self.key_edit) self.submit_button = gui.button(self.buttonsArea, self, "OK", self.accept) self.load_credentials() def load_credentials(self): self.key_edit.setPlainText(self.cm_key.key) def save_credentials(self): self.cm_key.key = self.key_edit.toPlainText() def accept(self): token = self.key_edit.toPlainText() if token: self.save_credentials() self.parent.update_api(token) super().accept() def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self.api = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept() # Set API key button gui.button( self.controlArea, self, "Twitter API Key", callback=self.open_key_dialog, tooltip="Set the API key for this widget.", focusPolicy=Qt.NoFocus, ) # Query query_box = gui.hBox(self.controlArea, "Query") layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1 + i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS - 1) ROW += 1 # Query input add_row( "Query word list:", ListEdit( self, "word_list", "Multiple lines are joined with OR.", 80, self, ), ) # Search mode add_row( "Search by:", gui.comboBox(self, self, "mode", items=self.MODES, callback=self.mode_toggle), ) # Language langs = (("Any", None), ) + tuple( (code2lang[l], l) for l in SUPPORTED_LANGUAGES) self.language_combo = ComboBox(self, "language", items=langs) add_row("Language:", self.language_combo) # Max tweets add_row( "Max tweets:", gui.spin( self, self, "max_tweets", minv=1, maxv=10000, checked="limited_search", ), ) # Retweets self.retweets_checkbox = gui.checkBox(self, self, "allow_retweets", "", minimumHeight=30) add_row("Allow retweets:", self.retweets_checkbox) # Collect Results add_row("Collect results:", gui.checkBox(self, self, "collecting", "")) query_box.layout().addLayout(layout) # Buttons self.search_button = gui.button( self.buttonsArea, self, "Search", self.start_stop, focusPolicy=Qt.NoFocus, ) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit def open_key_dialog(self): self.api_dlg.exec_() def mode_toggle(self): if self.mode == self.AUTHOR: self.language_combo.setCurrentIndex(0) self.retweets_checkbox.setCheckState(False) self.retweets_checkbox.setEnabled(self.mode == self.CONTENT) self.language_combo.setEnabled(self.mode == self.CONTENT) def start_stop(self): if self.task: self.cancel() self.search_button.setText("Search") else: self.run_search() @gui_require("api", "key_missing") def run_search(self): self.Error.clear() self.Info.nut_enough_tweets.clear() self.search() def search(self): max_tweets = self.max_tweets if self.limited_search else None content = self.mode == self.CONTENT if not self.word_list: self.Error.empty_query("keywords" if content else "authors") self.Outputs.corpus.send(None) return self.start( search, self.api, max_tweets, self.word_list, self.collecting, self.language if content else None, self.allow_retweets if content else None, "content" if content else "authors", ) self.search_button.setText("Stop") def update_api(self, key): if key: self.Error.key_missing.clear() self.api = twitter.TwitterAPI(key) else: self.api = None def on_done(self, result_corpus): self.search_button.setText("Search") if (result_corpus is None # probably because of rate error at beginning # or fewer tweets than expected or self.mode == self.CONTENT and len(result_corpus) < self.max_tweets or self.mode == self.AUTHOR # for authors, we expect self.max_tweets for each author and len(result_corpus) < self.max_tweets * len(self.word_list)): self.Info.nut_enough_tweets() self.Outputs.corpus.send(result_corpus) def on_exception(self, ex): self.search_button.setText("Search") if isinstance(ex, NoAuthorError): self.Error.wrong_author(str(ex)) else: self.Error.api_error(str(ex)) def on_partial_result(self, _): pass @gui_require("api", "key_missing") def send_report(self): for task in self.api.search_history: self.report_items(task)
def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self.api = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept() # Set API key button gui.button( self.controlArea, self, "Twitter API Key", callback=self.open_key_dialog, tooltip="Set the API key for this widget.", focusPolicy=Qt.NoFocus, ) # Query query_box = gui.hBox(self.controlArea, "Query") layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1 + i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS - 1) ROW += 1 # Query input add_row( "Query word list:", ListEdit( self, "word_list", "Multiple lines are joined with OR.", 80, self, ), ) # Search mode add_row( "Search by:", gui.comboBox(self, self, "mode", items=self.MODES, callback=self.mode_toggle), ) # Language langs = (("Any", None), ) + tuple( (code2lang[l], l) for l in SUPPORTED_LANGUAGES) self.language_combo = ComboBox(self, "language", items=langs) add_row("Language:", self.language_combo) # Max tweets add_row( "Max tweets:", gui.spin( self, self, "max_tweets", minv=1, maxv=10000, checked="limited_search", ), ) # Retweets self.retweets_checkbox = gui.checkBox(self, self, "allow_retweets", "", minimumHeight=30) add_row("Allow retweets:", self.retweets_checkbox) # Collect Results add_row("Collect results:", gui.checkBox(self, self, "collecting", "")) query_box.layout().addLayout(layout) # Buttons self.search_button = gui.button( self.buttonsArea, self, "Search", self.start_stop, focusPolicy=Qt.NoFocus, ) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit
class OWTwitter(OWWidget, ConcurrentWidgetMixin): class APICredentialsDialog(OWWidget): name = "Twitter API Credentials" want_main_area = False resizing_enabled = False cm_key = CredentialManager("Twitter API Key") cm_secret = CredentialManager("Twitter API Secret") key_input = "" secret_input = "" class Error(OWWidget.Error): invalid_credentials = Msg("This credentials are invalid.") def __init__(self, parent): super().__init__() self.parent = parent self.credentials = None form = QFormLayout() form.setContentsMargins(5, 5, 5, 5) self.key_edit = gui.lineEdit(self, self, "key_input", controlWidth=400) form.addRow("Key:", self.key_edit) self.secret_edit = gui.lineEdit(self, self, "secret_input", controlWidth=400) form.addRow("Secret:", self.secret_edit) self.controlArea.layout().addLayout(form) self.submit_button = gui.button(self.controlArea, self, "OK", self.accept) self.load_credentials() def load_credentials(self): self.key_edit.setText(self.cm_key.key) self.secret_edit.setText(self.cm_secret.key) def save_credentials(self): self.cm_key.key = self.key_input self.cm_secret.key = self.secret_input def check_credentials(self): c = twitter.Credentials(self.key_input, self.secret_input) if self.credentials != c: if c.valid: self.save_credentials() else: c = None self.credentials = c def accept(self, silent=False): if not silent: self.Error.invalid_credentials.clear() self.check_credentials() if self.credentials and self.credentials.valid: self.parent.update_api(self.credentials) super().accept() elif not silent: self.Error.invalid_credentials() name = "Twitter" description = "Load tweets from the Twitter API." icon = "icons/Twitter.svg" priority = 150 class Outputs: corpus = Output("Corpus", Corpus) want_main_area = False resizing_enabled = False class Warning(OWWidget.Warning): no_text_fields = Msg("Text features are inferred when none selected.") class Error(OWWidget.Error): api_error = Msg("Api error ({})") rate_limit = Msg("Rate limit exceeded. Please try again later.") empty_authors = Msg("Please provide some authors.") wrong_authors = Msg("Query does not match Twitter user handle.") key_missing = Msg("Please provide a valid API key to get the data.") CONTENT, AUTHOR = 0, 1 MODES = ["Content", "Author"] word_list = Setting([]) mode = Setting(0) limited_search = Setting(True) max_tweets = Setting(100) language = Setting(None) allow_retweets = Setting(False) collecting = Setting(False) attributes = [f.name for f in twitter.TwitterAPI.string_attributes] text_includes = Setting([f.name for f in twitter.TwitterAPI.text_features]) def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self.api = None self.corpus = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept(silent=True) # Set API key button gui.button( self.controlArea, self, "Twitter API Key", callback=self.open_key_dialog, tooltip="Set the API key for this widget.", focusPolicy=Qt.NoFocus, ) # Query query_box = gui.hBox(self.controlArea, "Query") layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1 + i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS - 1) ROW += 1 # Query input add_row( "Query word list:", ListEdit( self, "word_list", "Multiple lines are joined with OR.", 80, self, ), ) # Search mode add_row( "Search by:", gui.comboBox(self, self, "mode", items=self.MODES, callback=self.mode_toggle), ) # Language self.language_combo = ComboBox( self, "language", items=(("Any", None), ) + tuple(sorted(lang2code.items())), ) add_row("Language:", self.language_combo) # Max tweets add_row( "Max tweets:", gui.spin( self, self, "max_tweets", minv=1, maxv=10000, checked="limited_search", ), ) # Retweets self.retweets_checkbox = gui.checkBox(self, self, "allow_retweets", "", minimumHeight=30) add_row("Allow retweets:", self.retweets_checkbox) # Collect Results add_row("Collect results:", gui.checkBox(self, self, "collecting", "")) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget( CheckListLayout( "Text includes", self, "text_includes", self.attributes, cols=2, callback=self.set_text_features, )) # Buttons self.button_box = gui.hBox(self.controlArea) self.search_button = gui.button( self.button_box, self, "Search", self.start_stop, focusPolicy=Qt.NoFocus, ) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit def open_key_dialog(self): self.api_dlg.exec_() def mode_toggle(self): if self.mode == self.AUTHOR: self.language_combo.setCurrentIndex(0) self.retweets_checkbox.setCheckState(False) self.retweets_checkbox.setEnabled(self.mode == self.CONTENT) self.language_combo.setEnabled(self.mode == self.CONTENT) def start_stop(self): if self.task: self.cancel() self.search_button.setText("Search") else: self.run_search() @gui_require("api", "key_missing") def run_search(self): self.Error.clear() self.search() def search(self): max_tweets = self.max_tweets if self.limited_search else 0 if self.mode == self.CONTENT: self.start( search, self.api, max_tweets, self.word_list, self.collecting, self.language, self.allow_retweets, "content", ) else: if not self.word_list: self.Error.empty_authors() return None if not any(a.startswith("@") for a in self.word_list): self.Error.wrong_authors() return None self.start( search, self.api, max_tweets, self.word_list, self.collecting, None, None, "authors", ) self.search_button.setText("Stop") def update_api(self, key): if key: self.Error.key_missing.clear() self.api = twitter.TwitterAPI(key) else: self.api = None def on_done(self, result): self.search_button.setText("Search") if result: self.info.set_output_summary(len(result), f"{len(result)} tweets on output") else: self.info.set_output_summary(self.info.NoOutput) self.corpus = result self.set_text_features() def on_exception(self, ex): self.search_button.setText("Search") if isinstance(ex, TweepError) and ex.response.status_code == 429: self.Error.rate_limit() else: self.Error.api_error(str(ex)) def on_partial_result(self, _): pass def set_text_features(self): self.Warning.no_text_fields.clear() if not self.text_includes: self.Warning.no_text_fields() if self.corpus is not None: vars_ = [ var for var in self.corpus.domain.metas if var.name in self.text_includes ] self.corpus.set_text_features(vars_ or None) self.Outputs.corpus.send(self.corpus) @gui_require("api", "key_missing") def send_report(self): for task in self.api.search_history: self.report_items(task)
class OWTwitter(OWConcurrentWidget): class APICredentialsDialog(OWWidget): name = 'Twitter API Credentials' want_main_area = False resizing_enabled = False cm_key = CredentialManager('Twitter API Key') cm_secret = CredentialManager('Twitter API Secret') key_input = '' secret_input = '' class Error(OWWidget.Error): invalid_credentials = Msg('This credentials are invalid.') def __init__(self, parent): super().__init__() self.parent = parent self.credentials = None form = QFormLayout() form.setContentsMargins(5, 5, 5, 5) self.key_edit = gui.lineEdit(self, self, 'key_input', controlWidth=400) form.addRow('Key:', self.key_edit) self.secret_edit = gui.lineEdit(self, self, 'secret_input', controlWidth=400) form.addRow('Secret:', self.secret_edit) self.controlArea.layout().addLayout(form) self.submit_button = gui.button(self.controlArea, self, 'OK', self.accept) self.load_credentials() def load_credentials(self): self.key_edit.setText(self.cm_key.key) self.secret_edit.setText(self.cm_secret.key) def save_credentials(self): self.cm_key.key = self.key_input self.cm_secret.key = self.secret_input def check_credentials(self): c = twitter.Credentials(self.key_input, self.secret_input) if self.credentials != c: if c.valid: self.save_credentials() else: c = None self.credentials = c def accept(self, silent=False): if not silent: self.Error.invalid_credentials.clear() self.check_credentials() if self.credentials and self.credentials.valid: self.parent.update_api(self.credentials) super().accept() elif not silent: self.Error.invalid_credentials() name = 'Twitter' description = 'Load tweets from the Twitter API.' icon = 'icons/Twitter.svg' priority = 25 outputs = [(IO.CORPUS, Corpus)] want_main_area = False resizing_enabled = False class Warning(OWWidget.Warning): no_text_fields = Msg('Text features are inferred when none selected.') class Error(OWWidget.Error): api = Msg('Api error ({})') rate_limit = Msg('Rate limit exceeded. Please try again later.') empty_authors = Msg('Please provide some authors.') key_missing = Msg('Please provide a valid API key to get the data.') tweets_info = 'Tweets on output: {}' CONTENT, AUTHOR = 0, 1 MODES = ['Content', 'Author'] word_list = Setting([]) mode = Setting(0) limited_search = Setting(True) max_tweets = Setting(100) language = Setting(None) allow_retweets = Setting(False) collecting = Setting(False) attributes = [f.name for f in twitter.TwitterAPI.string_attributes] text_includes = Setting([f.name for f in twitter.TwitterAPI.text_features]) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.api = None self.corpus = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept(silent=True) # Set API key button gui.button(self.controlArea, self, 'Twitter API Key', callback=self.open_key_dialog, tooltip='Set the API key for this widget.', focusPolicy=Qt.NoFocus) # Query query_box = gui.hBox(self.controlArea, 'Query') layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1+i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS-1) ROW += 1 # Query input add_row('Query word list:', ListEdit(self, 'word_list', 'Multiple lines are joined with OR.', 80, self)) # Search mode add_row('Search by:', gui.comboBox(self, self, 'mode', items=self.MODES, callback=self.mode_toggle)) # Language self.language_combo = ComboBox(self, 'language', items=(('Any', None),) + tuple(sorted(lang2code.items()))) add_row('Language:', self.language_combo) # Max tweets add_row('Max tweets:', gui.spin(self, self, 'max_tweets', minv=1, maxv=10000, checked='limited_search')) # Retweets self.retweets_checkbox = gui.checkBox(self, self, 'allow_retweets', '', minimumHeight=30) add_row('Allow retweets:', self.retweets_checkbox) # Collect Results add_row('Collect results:', gui.checkBox(self, self, 'collecting', '')) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget( CheckListLayout('Text includes', self, 'text_includes', self.attributes, cols=2, callback=self.set_text_features)) self.tweets_info_label = gui.label(self.controlArea, self, self.tweets_info.format(0), box='Info') # Buttons self.button_box = gui.hBox(self.controlArea) self.button_box.layout().addWidget(self.report_button) self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop, focusPolicy=Qt.NoFocus) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit def open_key_dialog(self): self.api_dlg.exec_() def mode_toggle(self): if self.mode == self.AUTHOR: self.language_combo.setCurrentIndex(0) self.retweets_checkbox.setCheckState(False) self.retweets_checkbox.setEnabled(self.mode == self.CONTENT) self.language_combo.setEnabled(self.mode == self.CONTENT) def start_stop(self): if self.running: self.stop() else: self.search() def update_api(self, key): if key: self.Error.key_missing.clear() self.api = twitter.TwitterAPI(key, on_error=self.Error.api, on_rate_limit=self.Error.rate_limit) else: self.api = None @gui_require('api', 'key_missing') @asynchronous(allow_partial_results=True) def search(self, on_progress, should_break): def progress_with_info(total, progress): if self.limited_search or self.mode == self.AUTHOR: on_progress(100 * progress) self.update_tweets_num(total) self.Error.clear() self.api.on_progress = progress_with_info self.api.should_break = should_break max_tweets = self.max_tweets if self.limited_search else 0 if self.mode == self.CONTENT: return self.api.search_content(max_tweets=max_tweets, content=self.word_list, lang=self.language, allow_retweets=self.allow_retweets, collecting=self.collecting) else: if not self.word_list: self.Error.empty_authors() return None return self.api.search_authors(max_tweets=max_tweets, authors=self.word_list, collecting=self.collecting) def on_start(self): self.search_button.setText('Stop') self.send(IO.CORPUS, None) if self.mode == self.CONTENT and not self.limited_search: self.progressBarFinished(None) def on_result(self, result): self.search_button.setText('Search') self.update_tweets_num(len(result) if result else 0) self.corpus = result self.set_text_features() def update_tweets_num(self, num=0): self.tweets_info_label.setText(self.tweets_info.format(num)) def set_text_features(self): self.Warning.no_text_fields.clear() if not self.text_includes: self.Warning.no_text_fields() if self.corpus is not None: vars_ = [var for var in self.corpus.domain.metas if var.name in self.text_includes] self.corpus.set_text_features(vars_ or None) self.send(IO.CORPUS, self.corpus) @gui_require('api', 'key_missing') def send_report(self): for task in self.api.search_history: self.report_items(task)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.api = None self.corpus = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept(silent=True) # Set API key button gui.button(self.controlArea, self, 'Twitter API Key', callback=self.open_key_dialog, tooltip='Set the API key for this widget.', focusPolicy=Qt.NoFocus) # Query query_box = gui.hBox(self.controlArea, 'Query') layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1+i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS-1) ROW += 1 # Query input add_row('Query word list:', ListEdit(self, 'word_list', 'Multiple lines are joined with OR.', 80, self)) # Search mode add_row('Search by:', gui.comboBox(self, self, 'mode', items=self.MODES, callback=self.mode_toggle)) # Language self.language_combo = ComboBox(self, 'language', items=(('Any', None),) + tuple(sorted(lang2code.items()))) add_row('Language:', self.language_combo) # Max tweets add_row('Max tweets:', gui.spin(self, self, 'max_tweets', minv=1, maxv=10000, checked='limited_search')) # Retweets self.retweets_checkbox = gui.checkBox(self, self, 'allow_retweets', '', minimumHeight=30) add_row('Allow retweets:', self.retweets_checkbox) # Collect Results add_row('Collect results:', gui.checkBox(self, self, 'collecting', '')) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget( CheckListLayout('Text includes', self, 'text_includes', self.attributes, cols=2, callback=self.set_text_features)) self.tweets_info_label = gui.label(self.controlArea, self, self.tweets_info.format(0), box='Info') # Buttons self.button_box = gui.hBox(self.controlArea) self.button_box.layout().addWidget(self.report_button) self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop, focusPolicy=Qt.NoFocus) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit
class OWTwitter(OWWidget): class APICredentialsDialog(OWWidget): name = 'Twitter API Credentials' want_main_area = False resizing_enabled = False cm_key = CredentialManager('Twitter API Key') cm_secret = CredentialManager('Twitter API Secret') key_input = '' secret_input = '' class Error(OWWidget.Error): invalid_credentials = Msg('This credentials are invalid.') def __init__(self, parent): super().__init__() self.parent = parent self.credentials = None form = QFormLayout() form.setContentsMargins(5, 5, 5, 5) self.key_edit = gui.lineEdit(self, self, 'key_input', controlWidth=400) form.addRow('Key:', self.key_edit) self.secret_edit = gui.lineEdit(self, self, 'secret_input', controlWidth=400) form.addRow('Secret:', self.secret_edit) self.controlArea.layout().addLayout(form) self.submit_button = gui.button(self.controlArea, self, 'OK', self.accept) self.load_credentials() def load_credentials(self): self.key_edit.setText(self.cm_key.key) self.secret_edit.setText(self.cm_secret.key) def save_credentials(self): self.cm_key.key = self.key_input self.cm_secret.key = self.secret_input def check_credentials(self): c = twitter.Credentials(self.key_input, self.secret_input) if self.credentials != c: if c.valid: self.save_credentials() else: c = None self.credentials = c def accept(self, silent=False): if not silent: self.Error.invalid_credentials.clear() self.check_credentials() if self.credentials and self.credentials.valid: self.parent.update_api(self.credentials) super().accept() elif not silent: self.Error.invalid_credentials() name = 'Twitter' description = 'Load tweets from the Twitter API.' icon = 'icons/Twitter.svg' priority = 150 class Outputs: corpus = Output("Corpus", Corpus) want_main_area = False resizing_enabled = False class Warning(OWWidget.Warning): no_text_fields = Msg('Text features are inferred when none selected.') class Error(OWWidget.Error): api = Msg('Api error ({})') rate_limit = Msg('Rate limit exceeded. Please try again later.') empty_authors = Msg('Please provide some authors.') key_missing = Msg('Please provide a valid API key to get the data.') tweets_info = 'Tweets on output: {}' CONTENT, AUTHOR = 0, 1 MODES = ['Content', 'Author'] word_list = Setting([]) mode = Setting(0) limited_search = Setting(True) max_tweets = Setting(100) language = Setting(None) allow_retweets = Setting(False) collecting = Setting(False) attributes = [f.name for f in twitter.TwitterAPI.string_attributes] text_includes = Setting([f.name for f in twitter.TwitterAPI.text_features]) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.api = None self.corpus = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept(silent=True) # Set API key button gui.button(self.controlArea, self, 'Twitter API Key', callback=self.open_key_dialog, tooltip='Set the API key for this widget.', focusPolicy=Qt.NoFocus) # Query query_box = gui.hBox(self.controlArea, 'Query') layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1 + i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS - 1) ROW += 1 # Query input add_row( 'Query word list:', ListEdit(self, 'word_list', 'Multiple lines are joined with OR.', 80, self)) # Search mode add_row( 'Search by:', gui.comboBox(self, self, 'mode', items=self.MODES, callback=self.mode_toggle)) # Language self.language_combo = ComboBox(self, 'language', items=(('Any', None), ) + tuple(sorted(lang2code.items()))) add_row('Language:', self.language_combo) # Max tweets add_row( 'Max tweets:', gui.spin(self, self, 'max_tweets', minv=1, maxv=10000, checked='limited_search')) # Retweets self.retweets_checkbox = gui.checkBox(self, self, 'allow_retweets', '', minimumHeight=30) add_row('Allow retweets:', self.retweets_checkbox) # Collect Results add_row('Collect results:', gui.checkBox(self, self, 'collecting', '')) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget( CheckListLayout('Text includes', self, 'text_includes', self.attributes, cols=2, callback=self.set_text_features)) self.tweets_info_label = gui.label(self.controlArea, self, self.tweets_info.format(0), box='Info') # Buttons self.button_box = gui.hBox(self.controlArea) self.button_box.layout().addWidget(self.report_button) self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop, focusPolicy=Qt.NoFocus) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit def open_key_dialog(self): self.api_dlg.exec_() def mode_toggle(self): if self.mode == self.AUTHOR: self.language_combo.setCurrentIndex(0) self.retweets_checkbox.setCheckState(False) self.retweets_checkbox.setEnabled(self.mode == self.CONTENT) self.language_combo.setEnabled(self.mode == self.CONTENT) def start_stop(self): if self.search.running: self.search.stop() else: self.run_search() @gui_require('api', 'key_missing') def run_search(self): self.search() @asynchronous def search(self): max_tweets = self.max_tweets if self.limited_search else 0 if self.mode == self.CONTENT: return self.api.search_content(max_tweets=max_tweets, content=self.word_list, lang=self.language, allow_retweets=self.allow_retweets, collecting=self.collecting) else: if not self.word_list: self.Error.empty_authors() return None return self.api.search_authors(max_tweets=max_tweets, authors=self.word_list, collecting=self.collecting) def update_api(self, key): if key: self.Error.key_missing.clear() self.api = twitter.TwitterAPI( key, on_error=self.Error.api, on_rate_limit=self.Error.rate_limit, should_break=self.search.should_break, on_progress=self.update_tweets_num) else: self.api = None @search.on_start def on_start(self): self.Error.clear() self.progressBarInit(None) self.search_button.setText('Stop') self.Outputs.corpus.send(None) if self.mode == self.CONTENT and not self.limited_search: self.progressBarFinished(None) @search.on_result def on_result(self, result): self.search_button.setText('Search') self.tweets_info_label.setText( self.tweets_info.format(len(result) if result else 0)) self.corpus = result self.set_text_features() self.progressBarFinished(None) @search.callback(should_raise=False) def update_tweets_num(self, num=0, progress=None): if self.limited_search or self.mode == self.AUTHOR: if progress is not None: self.progressBarSet(100 * progress, None) self.tweets_info_label.setText(self.tweets_info.format(num)) def set_text_features(self): self.Warning.no_text_fields.clear() if not self.text_includes: self.Warning.no_text_fields() if self.corpus is not None: vars_ = [ var for var in self.corpus.domain.metas if var.name in self.text_includes ] self.corpus.set_text_features(vars_ or None) self.Outputs.corpus.send(self.corpus) @gui_require('api', 'key_missing') def send_report(self): for task in self.api.search_history: self.report_items(task)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.api = None self.corpus = None self.api_dlg = self.APICredentialsDialog(self) self.api_dlg.accept(silent=True) # Set API key button gui.button(self.controlArea, self, 'Twitter API Key', callback=self.open_key_dialog, tooltip='Set the API key for this widget.', focusPolicy=Qt.NoFocus) # Query query_box = gui.hBox(self.controlArea, 'Query') layout = QGridLayout() layout.setVerticalSpacing(5) layout.setColumnStretch(2, 1) # stretch last columns layout.setColumnMinimumWidth(1, 15) # add some space for checkbox ROW = 0 COLUMNS = 3 def add_row(label, items): nonlocal ROW, COLUMNS layout.addWidget(QLabel(label), ROW, 0) if isinstance(items, tuple): for i, item in enumerate(items): layout.addWidget(item, ROW, 1 + i) else: layout.addWidget(items, ROW, 1, 1, COLUMNS - 1) ROW += 1 # Query input add_row( 'Query word list:', ListEdit(self, 'word_list', 'Multiple lines are joined with OR.', 80, self)) # Search mode add_row( 'Search by:', gui.comboBox(self, self, 'mode', items=self.MODES, callback=self.mode_toggle)) # Language self.language_combo = ComboBox(self, 'language', items=(('Any', None), ) + tuple(sorted(lang2code.items()))) add_row('Language:', self.language_combo) # Max tweets add_row( 'Max tweets:', gui.spin(self, self, 'max_tweets', minv=1, maxv=10000, checked='limited_search')) # Retweets self.retweets_checkbox = gui.checkBox(self, self, 'allow_retweets', '', minimumHeight=30) add_row('Allow retweets:', self.retweets_checkbox) # Collect Results add_row('Collect results:', gui.checkBox(self, self, 'collecting', '')) query_box.layout().addLayout(layout) self.controlArea.layout().addWidget( CheckListLayout('Text includes', self, 'text_includes', self.attributes, cols=2, callback=self.set_text_features)) self.tweets_info_label = gui.label(self.controlArea, self, self.tweets_info.format(0), box='Info') # Buttons self.button_box = gui.hBox(self.controlArea) self.button_box.layout().addWidget(self.report_button) self.search_button = gui.button(self.button_box, self, 'Search', self.start_stop, focusPolicy=Qt.NoFocus) self.mode_toggle() self.setFocus() # to widget itself to show placeholder for query_edit