diff --git a/.gitignore b/.gitignore
index 17da1fd..9fb491e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,11 +52,19 @@ venv/
ENV/
env.bak/
venv.bak/
+.envrc
# ide
.vscode
.idea
+# poetry
+/poetry.toml
+
# user
-link.sh
-info.plist.bak
+/link.sh
+/info.plist.bak
+/*/.site-packages
+/build/
+/requirements.txt
+*.alfredworkflow
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 8c59060..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "alfred-workflow"]
- path = alfred-workflow
- url = https://github.com/bskim45/alfred-workflow.git
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..7f94c63
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,24 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: check-executables-have-shebangs
+
+ - repo: https://gitlab.com/pycqa/flake8
+ rev: 3.9.2
+ hooks:
+ - id: flake8
+ additional_dependencies: [flake8-bugbear]
+
+ - repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+
+ - repo: https://github.com/psf/black
+ rev: 22.3.0
+ hooks:
+ - id: black
diff --git a/.versionrc.json b/.versionrc.json
index 256fa8a..aae93d8 100644
--- a/.versionrc.json
+++ b/.versionrc.json
@@ -13,7 +13,7 @@
}
],
"scripts": {
- "postbump": "sed -i.bak \"s#[0-9]*.[0-9]*.[0-9]*#$(cat version)#\" info.plist",
+ "postbump": "poetry version $(cat version) && sed -i.bak \"s#[0-9]*.[0-9]*.[0-9]*#$(cat version)#\" info.plist",
"precommit": "git add info.plist"
}
diff --git a/LICENSE b/LICENSE
old mode 100755
new mode 100644
diff --git a/Makefile b/Makefile
index aadd6a4..b9da36a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,24 +1,55 @@
NAME := alfred-coin-ticker
WORKFLOW_FILENAME := $(NAME).alfredworkflow
VERSION_FILE := version
-TARGET_FILES := $(shell cat includes.list)
+TARGET_FILES := $(shell cat include.list)
-default: build
+# must use system python
+PYTHON := /usr/bin/python3
+
+default: clean build
+
+.PHONY: .venv
+.venv:
+ poetry env use $(PYTHON)
+ poetry install
.PHONY: build
build: $(WORKFLOW_FILENAME)
-$(WORKFLOW_FILENAME): $(TARGET_FILES)
+requirements.txt:
+ poetry export --without-hashes > requirements.txt
+
+%/.site-packages:
+ $(PYTHON) -m pip install \
+ --prefer-binary \
+ --upgrade \
+ --target=$@ \
+ ${PACKAGES}
+
+deps: requirements.txt
+deps: PACKAGES=-r requirements.txt
+deps: build/.site-packages
+deps: $(TARGET_FILES)
+
+$(WORKFLOW_FILENAME): deps
@echo "> Packaging..."
- ./build.sh
+ rm -f $(WORKFLOW_FILENAME)
+ BUILD_DIR=$(BUILD_DIR) WORKFLOW_FILENAME=$(WORKFLOW_FILENAME) ./build.sh
.PHONY: test
test:
python -m unittest discover -s tests -v
+install: $(WORKFLOW_FILENAME)
+ open $(WORKFLOW_FILENAME)
+
.PHONY: clean
clean:
- rm -f $(WORKFLOW_FILENAME)
+ rm -rf \
+ requirements.txt \
+ ./build/ \
+ .mypy_cache/ \
+ $(WORKFLOW_FILENAME)
.PHONY: bump_version
bump_version:
@@ -29,6 +60,7 @@ bump_version:
@echo Current version: $(shell cat $(VERSION_FILE))
@(read -e -p "Bump to version $(version)? [y/N]: " ans && case "$$ans" in [yY]) true;; *) false;; esac)
@echo $(version) > version
+ @potry version $(version)
@sed -i.bak 's#[0-9]*.[0-9]*.[0-9]*#$(version)#' info.plist
.PHONY: release
@@ -46,3 +78,7 @@ release-minor:
.PHONY: release-patch
release-patch:
@standard-version -a -s -t "" --release-as patch
+
+EXECUTABLES = $(PYTHON) plutil open rsync poetry
+K := $(foreach exec,$(EXECUTABLES),\
+ $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index bb11ff3..c8ee070
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
Coin Ticker for Alfred Workflow
-An [Alfred Workflow](http://www.alfredapp.com/) that provides the current price
+An [Alfred Workflow](http://www.alfredapp.com/) that provides the current price
and status about cryptocurrency from [cryptocompare.com].
Supports Alfred 3 and Alfred 4 on macOS 10.7+ (Python 2.7).
@@ -52,8 +52,8 @@ Please use with caution.
The code is released under the MIT license. See [LICENSE](LICENSE) for details.
-Awesome [alfred-workflow](https://github.com/deanishe/alfred-workflow) library
-by [@deanishe](https://github.com/deanishe) is also released under
+Awesome [alfred-workflow](https://github.com/deanishe/alfred-workflow) library
+by [@deanishe](https://github.com/deanishe) is also released under
[MIT License](alfred-workflow/LICENCE.txt).
[cryptocompare.com]: https://www.cryptocompare.com/
diff --git a/alfred-workflow b/alfred-workflow
deleted file mode 160000
index 8110e88..0000000
--- a/alfred-workflow
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 8110e885e1ac3028bfd82d19f3bd59de78752201
diff --git a/api.py b/api.py
index 7c88350..2c97921 100755
--- a/api.py
+++ b/api.py
@@ -1,34 +1,35 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2022 Bumsoo Kim
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
import os
from collections import namedtuple
+import requests as requests
+
from utils import clean_price_string
-from workflow import web
class CoinTick(object):
def __init__(
self,
- ticker, # type: unicode
- symbol, # type: unicode
- image_url, # type: unicode
- fiat, # type: unicode
- fiat_symbol, # type: unicode
- price, # type: unicode
- price_24h_high, # type: unicode
- price_24h_low, # type: unicode
- price_change_24h, # type: unicode
- price_change_24h_percent, # type: unicode
- total_volume_24h, # type: unicode
- total_volume_24h_fiat, # type: unicode
+ ticker, # type: str
+ symbol, # type: str
+ image_url, # type: str
+ fiat, # type: str
+ fiat_symbol, # type: str
+ price, # type: str
+ price_24h_high, # type: str
+ price_24h_low, # type: str
+ price_change_24h, # type: str
+ price_change_24h_percent, # type: str
+ total_volume_24h, # type: str
+ total_volume_24h_fiat, # type: str
):
self.ticker = ticker
self.symbol = symbol
@@ -47,32 +48,31 @@ def __eq__(self, o):
if not isinstance(o, CoinTick):
return False
- return all((
- self.ticker == o.ticker,
- self.symbol == o.symbol,
- self.image_url == o.image_url,
- self.fiat == o.fiat,
- self.fiat_symbol == o.fiat_symbol,
- self.price == o.price,
- self.price_24h_high == o.price_24h_high,
- self.price_24h_low == o.price_24h_low,
- self.price_change_24h == o.price_change_24h,
- self.price_change_24h_percent == o.price_change_24h_percent,
- self.total_volume_24h == o.total_volume_24h,
- self.total_volume_24h_fiat == o.total_volume_24h_fiat,
- ))
-
- def __unicode__(self):
- return '{0} {1} ({2}{3})'.format(
- self.symbol, self.ticker, self.fiat_symbol, self.price)
+ return all(
+ (
+ self.ticker == o.ticker,
+ self.symbol == o.symbol,
+ self.image_url == o.image_url,
+ self.fiat == o.fiat,
+ self.fiat_symbol == o.fiat_symbol,
+ self.price == o.price,
+ self.price_24h_high == o.price_24h_high,
+ self.price_24h_low == o.price_24h_low,
+ self.price_change_24h == o.price_change_24h,
+ self.price_change_24h_percent == o.price_change_24h_percent,
+ self.total_volume_24h == o.total_volume_24h,
+ self.total_volume_24h_fiat == o.total_volume_24h_fiat,
+ )
+ )
def __str__(self):
- return unicode(self).encode('utf-8')
+ return '{0} {1} ({2}{3})'.format(
+ self.symbol, self.ticker, self.fiat_symbol, self.price
+ )
CoinInfo = namedtuple(
- 'CoinInfo',
- ['name', 'ticker', 'symbol', 'image_url', 'url']
+ 'CoinInfo', ['name', 'ticker', 'symbol', 'image_url', 'url']
)
@@ -84,31 +84,30 @@ class TickClient(object):
@classmethod
def get_cache_key(cls, query=None):
- return '{0}-{1}'.format(cls.CACHE_KEY, query) \
- .replace(os.sep, '_')
+ return '{0}-{1}'.format(cls.CACHE_KEY, query).replace(os.sep, '_')
def get(self, path, params):
- # type: (unicode, dict) -> dict
- r = web.get(self.API_BASE_URL + path, params)
+ # type: (str, dict) -> dict
+ r = requests.get(self.API_BASE_URL + path, params)
r.raise_for_status()
result = r.json()
return result
def get_coin_prices(self, tickers, fiat):
- # type: (list[unicode], unicode) -> list[CoinTick] # noqa
+ # type: (list[str], str) -> list[CoinTick] # noqa
raise NotImplementedError()
def get_top_market_cap(self, limit, fiat):
- # type: (int, unicode) -> list[CoinTick] # noqa
+ # type: (int, str) -> list[CoinTick] # noqa
raise NotImplementedError()
def get_coin_info(self, ticker):
- # type: (unicode) -> CoinInfo # noqa
+ # type: (str) -> CoinInfo # noqa
raise NotImplementedError()
def get_ticker_web_url(self, ticker, fiat):
- # type: (unicode, unicode) -> unicode
+ # type: (str, str) -> str
raise NotImplementedError()
@@ -168,9 +167,11 @@ def get_ticker_web_url(self, ticker, fiat):
def coin_info_from_api_repr(cls, data):
# type: (dict) -> CoinInfo
return CoinInfo(
- data['CoinName'], data['Symbol'], None,
+ data['CoinName'],
+ data['Symbol'],
+ None,
cls.WEB_BASE_URL + data['ImageUrl'],
- cls.WEB_BASE_URL + data['Url']
+ cls.WEB_BASE_URL + data['Url'],
)
@classmethod
@@ -183,9 +184,11 @@ def tick_from_api_repr(cls, raw, display):
fiat_symbol = display.get('TOSYMBOL')
return CoinTick(
- ticker, symbol,
+ ticker,
+ symbol,
cls.WEB_BASE_URL + display.get('IMAGEURL'),
- fiat, fiat_symbol,
+ fiat,
+ fiat_symbol,
clean_price_string(fiat_symbol, display.get('PRICE')),
clean_price_string(fiat_symbol, display.get('HIGH24HOUR')),
clean_price_string(fiat_symbol, display.get('LOW24HOUR')),
@@ -222,5 +225,5 @@ def get_coin_web_url(cls, coin_name):
@staticmethod
def normalize_to_underscore(name):
- # type: (unicode) -> unicode
+ # type: (str) -> str
return name.replace('.', '-').replace(' ', '-')
diff --git a/build.sh b/build.sh
index 1989695..5398204 100755
--- a/build.sh
+++ b/build.sh
@@ -1,25 +1,36 @@
#!/usr/bin/env bash
-
-WORKFLOW_NAME=alfred-coin-ticker.alfredworkflow
+set -euo pipefail
SCRIPT_HOME=$(dirname "$(realpath "$0")")
VERSION=$(cat version)
+: "${BUILD_DIR:="$SCRIPT_HOME/build"}"
+: "${WORKFLOW_FILENAME:=alfred-coin-ticker.alfredworkflow}"
echo "Home path: $SCRIPT_HOME"
echo "Version: $VERSION"
+echo "Build dir: $BUILD_DIR"
+echo "Workflow: $WORKFLOW_FILENAME"
+
+#read -p "Continue? [y/N]" -n 1 -r
+#echo # new line
+#if [[ $REPLY =~ ^[Yy]$ ]]; then
+# # pass
+# echo "Building..."
+#else
+# echo "abort"
+# exit 1
+#fi
-cd "$SCRIPT_HOME/alfred-workflow/workflow" || exit
-git clean -nx
+rsync --archive --verbose \
+ --filter '- *.pyc' \
+ --filter '- *.egg-info' \
+ --filter '- *.dist-info' \
+ --filter '- __pycache__' \
+ "$BUILD_DIR/.site-packages/" "$BUILD_DIR/"
-read -p "Continue? [y/N]" -n 1 -r
-echo # new line
-if [[ $REPLY =~ ^[Yy]$ ]]; then
- git clean -fx
-else
- echo "abort"
- exit 1
-fi
+# shellcheck disable=SC2046
+cp $(runningsubtext
fetching...
script
- python main.py {query}
+ /usr/bin/env python3 main.py {query}
scriptargtype
0
scriptfile
diff --git a/main.py b/main.py
index b955105..09ab624 100644
--- a/main.py
+++ b/main.py
@@ -1,50 +1,70 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2022 Bumsoo Kim
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals, division
+from __future__ import annotations
import multiprocessing
import sys
from multiprocessing.pool import ThreadPool
-from api import CryptoCompareClient, CoinTick, CoinInfo, CoinMarketCapClient
-from utils import create_workflow, cached_file, get_display_change_string, \
- cached_file_fresh
-from workflow import Workflow3, ICON_INFO, ICON_WEB, web, \
- ICON_SETTINGS, ICON_ERROR, ICON_TRASH
+import requests
+from workflow import (
+ ICON_ERROR,
+ ICON_INFO,
+ ICON_SETTINGS,
+ ICON_TRASH,
+ ICON_WEB,
+ Workflow3,
+)
+
+from api import CoinInfo, CoinMarketCapClient, CoinTick, CryptoCompareClient
+from utils import (
+ cached_file,
+ cached_file_fresh,
+ create_workflow,
+ get_display_change_string,
+)
TICKER_CACHE_AGE_SECONDS = 14 * 24 * 60 * 60 # 2 weeks
class Command(object):
- def __init__(self, command_id, alfred_command, help_message, icon=None):
- # type: (unicode, unicode, unicode, unicode) -> Command
+ def __init__(
+ self,
+ command_id: str,
+ alfred_command: str,
+ help_message: str,
+ icon: str = None,
+ ):
self.command_id = command_id
self.alfred_command = alfred_command
self.help_message = help_message
self.icon = icon
- def is_command(self, command):
- # type: (unicode) -> bool
+ def is_command(self, command: str) -> bool:
return command == self.alfred_command
CMD_LIST_FAVORITES = Command('list_favorites', '', 'List favorite coins.')
-CMD_LIST_RANKINGS = Command('list_rankings', 'list',
- 'Toplist by Market Cap.')
-CMD_SET_CURRENCY = Command('set_currency', 'set currency', 'Set currency',
- ICON_SETTINGS)
+CMD_LIST_RANKINGS = Command('list_rankings', 'list', 'Toplist by Market Cap.')
+CMD_SET_CURRENCY = Command(
+ 'set_currency', 'set currency', 'Set currency', ICON_SETTINGS
+)
CMD_HELP = Command('show_help', 'help', 'Show help', ICON_INFO)
-CMD_RESET = Command('reset', 'reset',
- 'Reset all settings and delete all caches/data.',
- ICON_TRASH)
+CMD_RESET = Command(
+ 'reset',
+ 'reset',
+ 'Reset all settings and delete all caches/data.',
+ ICON_TRASH,
+)
CMD_ADD_COIN = Command('add_coin', 'add', 'Add new coin to the favorites.')
-CMD_REMOVE_COIN = Command('remove_coin', 'remove',
- 'Remove a coin from the favorites.')
+CMD_REMOVE_COIN = Command(
+ 'remove_coin', 'remove', 'Remove a coin from the favorites.'
+)
COMMAND_LIST = [
CMD_LIST_FAVORITES,
@@ -91,10 +111,10 @@ def get_info(ticker):
def get_coin_image_multi(ticker_and_image_urls):
- # type: (list[tuple[unicode, unicode]]) -> list[tuple[unicode, str]] # noqa
+ # type: (list[tuple[str, str]]) -> list[tuple[str, str]] # noqa
def get_image(ticker_and_image_url):
ticker, image_url = ticker_and_image_url
- return ticker, web.get(image_url).content
+ return ticker, requests.get(image_url).content
pool = ThreadPool(multiprocessing.cpu_count() // 2)
ticker_and_images = pool.map(get_image, ticker_and_image_urls)
@@ -102,13 +122,13 @@ def get_image(ticker_and_image_url):
def get_coin_info_from_tickers(wf, tickers):
- # type: (Workflow3, list[unicode]) -> dict[unicode, CoinInfo] # noqa
+ # type: (Workflow3, list[str]) -> dict[str, CoinInfo] # noqa
ticker_map = {}
unknown_tickers = []
- def get_cache_key(ticker):
- return '{0}_info'.format(ticker.lower()).encode('utf-8')
+ def get_cache_key(ticker) -> str:
+ return '{0}_info'.format(ticker.lower())
for ticker in tickers:
is_cached = wf.cached_data_fresh(
@@ -116,8 +136,9 @@ def get_cache_key(ticker):
) # type: CoinInfo
if is_cached:
- ticker_info = wf.cached_data(get_cache_key(ticker), None,
- TICKER_CACHE_AGE_SECONDS)
+ ticker_info = wf.cached_data(
+ get_cache_key(ticker), None, TICKER_CACHE_AGE_SECONDS
+ )
ticker_map[ticker] = ticker_info
else:
unknown_tickers.append(ticker)
@@ -127,42 +148,48 @@ def get_cache_key(ticker):
for ticker_info in unknown_coin_info_list:
# replace web link to coinmarketcap
ticker_info = ticker_info._replace( # noqa
- url=CoinMarketCapClient.get_coin_web_url(
- ticker_info.name.lower())
+ url=CoinMarketCapClient.get_coin_web_url(ticker_info.name.lower())
+ )
+ wf.cached_data(
+ get_cache_key(ticker_info.ticker),
+ lambda: ticker_info,
+ TICKER_CACHE_AGE_SECONDS,
)
- wf.cached_data(get_cache_key(ticker_info.ticker), lambda: ticker_info,
- TICKER_CACHE_AGE_SECONDS)
ticker_map[ticker_info.ticker] = ticker_info
return ticker_map
def get_coin_image_from_tickers(wf, ticker_and_image_list):
- # type: (Workflow3, list[tuple[unicode, unicode]]) -> dict[unicode, unicode] # noqa
+ # type: (Workflow3, list[tuple[str, str]]) -> dict[str, str] # noqa
ticker_image_path_map = {}
unknown_ticker_and_image_urls = []
def get_cache_key(ticker):
- return '{0}_image'.format(ticker.lower()).encode('utf-8')
+ return '{0}_image'.format(ticker.lower())
for ticker, image_url in ticker_and_image_list:
- is_cached = cached_file_fresh(wf, get_cache_key(ticker),
- TICKER_CACHE_AGE_SECONDS)
+ is_cached = cached_file_fresh(
+ wf, get_cache_key(ticker), TICKER_CACHE_AGE_SECONDS
+ )
if is_cached:
- image_path = cached_file(wf, get_cache_key(ticker), None,
- TICKER_CACHE_AGE_SECONDS)
+ image_path = cached_file(
+ wf, get_cache_key(ticker), None, TICKER_CACHE_AGE_SECONDS
+ )
ticker_image_path_map[ticker] = image_path
else:
unknown_ticker_and_image_urls.append((ticker, image_url))
unknown_ticker_image_list = get_coin_image_multi(
- unknown_ticker_and_image_urls)
+ unknown_ticker_and_image_urls
+ )
for ticker, image in unknown_ticker_image_list:
- image_path = cached_file(wf, get_cache_key(ticker), lambda: image,
- TICKER_CACHE_AGE_SECONDS)
+ image_path = cached_file(
+ wf, get_cache_key(ticker), lambda: image, TICKER_CACHE_AGE_SECONDS
+ )
ticker_image_path_map[ticker] = image_path
return ticker_image_path_map
@@ -181,25 +208,29 @@ def add_ticks_to_workflow(wf, ticks):
for tick in ticks:
item = wf.add_item(
- title='{ticker:<5}\t{fiat}{price:10}\t({change_pct}%) \t{symbol} {volume}'.format(
- # noqa
- ticker=tick.ticker, fiat=tick.fiat_symbol, price=tick.price,
+ title='{ticker:<5}\t{fiat}{price:10}\t({change_pct}%) \t{symbol} {volume}'.format( # noqa pylint: disable=line-too-long
+ ticker=tick.ticker,
+ fiat=tick.fiat_symbol,
+ price=tick.price,
change_pct=get_display_change_string(
- tick.price_change_24h_percent),
+ tick.price_change_24h_percent
+ ),
symbol=tick.symbol,
volume=tick.total_volume_24h,
),
subtitle='24h High {0}{1} | Low {0}{2} | Change {3}'.format(
tick.fiat_symbol,
- tick.price_24h_high, tick.price_24h_low,
+ tick.price_24h_high,
+ tick.price_24h_low,
get_display_change_string(tick.price_change_24h),
),
arg=coin_info_map[tick.ticker].url,
valid=True,
icon=coin_image_path_map[tick.ticker],
)
- item.add_modifier('cmd', 'Copy ticker price',
- arg=tick.price.replace(',', ''))
+ item.add_modifier(
+ 'cmd', 'Copy ticker price', arg=tick.price.replace(',', '')
+ )
item.add_modifier('alt', 'Copy ticker', arg=tick.ticker)
return len(ticks) > 0
@@ -216,12 +247,14 @@ def _get():
add_ticks_to_workflow(
wf,
- wf.cached_data(b'market_cap_rankings_10', _get, max_age=3, session=True)
+ wf.cached_data(
+ 'market_cap_rankings_10', _get, max_age=3, session=True
+ ),
)
def list_tickers(wf, tickers):
- # type: (Workflow3, list[unicode]) -> () # noqa
+ # type: (Workflow3, list[str]) -> () # noqa
if not tickers:
return
@@ -233,8 +266,12 @@ def _get():
is_not_empty = add_ticks_to_workflow(
wf,
- wf.cached_data(b'tickers_{0}'.format('_'.join(tickers)), _get,
- max_age=3, session=True),
+ wf.cached_data(
+ 'tickers_{0}'.format('_'.join(tickers)),
+ _get,
+ max_age=3,
+ session=True,
+ ),
)
return is_not_empty
@@ -284,10 +321,10 @@ def show_add_favorites_help(wf):
def add_favorites_prompt(wf, ticker, position_str=None):
- # type: (Workflow3, unicode, Optional[unicode]) -> () # noqa
+ # type: (Workflow3, str, Optional[str]) -> () # noqa
ticker = ticker.upper()
- favorites = wf.settings['favorites'] # type: list[unicode] # noqa
+ favorites = wf.settings['favorites'] # type: list[str] # noqa
if ticker in favorites:
wf.add_item(
@@ -307,26 +344,26 @@ def add_favorites_prompt(wf, ticker, position_str=None):
if position:
wf.add_item(
title="Add '{0}' to the favorites at position {1}.".format(
- ticker, position),
+ ticker, position
+ ),
subtitle='press ENTER to proceed',
autocomplete='add_commit {0} {1}'.format(ticker, position),
icon=ICON_INFO,
)
else:
wf.add_item(
- title="Add '{0}' to the end of the favorites.".format(
- ticker, position),
+ title="Add '{0}' to the end of the favorites.".format(ticker),
subtitle='press ENTER to proceed',
- autocomplete='add_commit {0}'.format(ticker, position),
+ autocomplete='add_commit {0}'.format(ticker),
icon=ICON_INFO,
)
def add_favorites(wf, ticker, position_str=None):
- # type: (Workflow3, unicode, Optional[unicode]) -> () # noqa
+ # type: (Workflow3, str, Optional[str]) -> () # noqa
ticker = ticker.upper()
- favorites = wf.settings['favorites'] # type: list[unicode] # noqa
+ favorites = wf.settings['favorites'] # type: list[str] # noqa
position = int(position_str) if position_str else None
if position:
@@ -346,10 +383,10 @@ def add_favorites(wf, ticker, position_str=None):
def remove_favorites(wf, ticker):
- # type: (Workflow3, unicode) -> () # noqa
+ # type: (Workflow3, str) -> () # noqa
ticker = ticker.upper()
- favorites = wf.settings['favorites'] # type: list[unicode] # noqa
+ favorites = wf.settings['favorites'] # type: list[str] # noqa
if ticker in favorites:
favorites.remove(ticker)
@@ -365,7 +402,7 @@ def remove_favorites(wf, ticker):
def set_currency(wf, fiat):
- # type: (Workflow3, Optional[unicode]) -> () # noqa
+ # type: (Workflow3, Optional[str]) -> () # noqa
if not fiat:
wf.warn_empty(
title='Set currency',
@@ -377,8 +414,10 @@ def set_currency(wf, fiat):
fiat = fiat.upper()
if len(fiat) != 3:
- wf.warn_empty(title="'{0}' is invalid currency.".format(fiat),
- icon=ICON_ERROR)
+ wf.warn_empty(
+ title="'{0}' is invalid currency.".format(fiat),
+ icon=ICON_ERROR,
+ )
else:
wf.settings['currency'] = fiat
wf.add_item(
@@ -499,10 +538,12 @@ def main(wf):
show_default_items = True
if wf.update_available:
- wf.add_item('New version is available',
- subtitle='Click to install the update',
- autocomplete='workflow:update',
- icon=ICON_INFO)
+ wf.add_item(
+ 'New version is available',
+ subtitle='Click to install the update',
+ autocomplete='workflow:update',
+ icon=ICON_INFO,
+ )
if show_default_items:
add_default_item(wf)
diff --git a/poetry.lock b/poetry.lock
index 6d4f92c..71ddd00 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,11 +1,49 @@
[[package]]
-name = "funcsigs"
-version = "1.0.2"
-description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
-category = "dev"
+name = "Alfred-Workflow"
+version = "1.40.0"
+description = "Full-featured helper library for writing Alfred 2/3/4 workflows"
+category = "main"
+optional = false
+python-versions = "*"
+develop = false
+
+[package.dependencies]
+requests = ">=2.25,<3"
+six = "*"
+
+[package.source]
+type = "git"
+url = "https://github.com/NorthIsUp/alfred-workflow-py3"
+reference = "master"
+resolved_reference = "b1b76f025ce8317cac3de435836c7194f12964fe"
+
+[[package]]
+name = "certifi"
+version = "2021.10.8"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "charset-normalizer"
+version = "2.0.12"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
[[package]]
name = "mock"
version = "3.0.5"
@@ -15,7 +53,6 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
-funcsigs = {version = ">=1", markers = "python_version < \"3.3\""}
six = "*"
[package.extras]
@@ -23,29 +60,77 @@ build = ["twine", "wheel", "blurb"]
docs = ["sphinx"]
test = ["pytest", "pytest-cov"]
+[[package]]
+name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+[[package]]
+name = "urllib3"
+version = "1.26.9"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
[metadata]
lock-version = "1.1"
-python-versions = "~2.7"
-content-hash = "83d66b7817e8858047aa6e8ac2ffdbffea50014a72e2ec5fad80eaa86ba0cef5"
+python-versions = "~3.8"
+content-hash = "3a9365d0076e262323747b73cf2cfe786d3b39d4a305cae87db87e16bdc392c1"
[metadata.files]
-funcsigs = [
- {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
- {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"},
+Alfred-Workflow = []
+certifi = [
+ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+]
+charset-normalizer = [
+ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
+]
+idna = [
+ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
mock = [
{file = "mock-3.0.5-py2.py3-none-any.whl", hash = "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"},
{file = "mock-3.0.5.tar.gz", hash = "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3"},
]
+requests = [
+ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
+urllib3 = [
+ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
+ {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 3ad4b99..e154616 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,14 @@
[tool.poetry]
name = "alfred-coin-ticker"
-version = "1.0.0"
+version = "1.1.0"
description = ""
authors = ["Bumsoo Kim "]
license = "MIT"
[tool.poetry.dependencies]
-python = "~2.7"
+python = "~3.8"
+requests = "^2.27.1"
+alfred-workflow = {git = "https://github.com/NorthIsUp/alfred-workflow-py3", rev = "master"}
[tool.poetry.dev-dependencies]
mock = "~3.0.5"
@@ -14,3 +16,20 @@ mock = "~3.0.5"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+profile = "black"
+line_length = 79
+
+[tool.black]
+line-length = 79
+skip-string-normalization = true
+
+[tool.pytest.ini_options]
+addopts = "-p no:warnings"
+
+[tool.coverage.run]
+parallel = true
+omit = [
+ "tests/*",
+]
diff --git a/tests/test_api_coinmarketcap.py b/tests/test_api_coinmarketcap.py
index 1e8bb83..843823f 100644
--- a/tests/test_api_coinmarketcap.py
+++ b/tests/test_api_coinmarketcap.py
@@ -5,7 +5,7 @@
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
from unittest import TestCase
diff --git a/tests/test_api_cointick.py b/tests/test_api_cointick.py
index 799a982..5213175 100644
--- a/tests/test_api_cointick.py
+++ b/tests/test_api_cointick.py
@@ -5,7 +5,7 @@
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
from unittest import TestCase
@@ -15,20 +15,32 @@
class CoinTickTest(TestCase):
def test_operator_eq(self):
a = CoinTick(
- 'BTC', 'Ƀ',
+ 'BTC',
+ 'Ƀ',
'https://www.cryptocompare.com/media/37746251/btc.png',
- 'USD', '$',
- '43,022.2', '43,355.5', '41,761.2',
- '-253.25', '-0.59',
- '158.53 K', '6.81 B',
+ 'USD',
+ '$',
+ '43,022.2',
+ '43,355.5',
+ '41,761.2',
+ '-253.25',
+ '-0.59',
+ '158.53 K',
+ '6.81 B',
)
b = CoinTick(
- 'BTC', 'Ƀ',
+ 'BTC',
+ 'Ƀ',
'https://www.cryptocompare.com/media/37746251/btc.png',
- 'USD', '$',
- '43,022.2', '43,355.5', '41,761.2',
- '-253.25', '-0.59',
- '158.53 K', '6.81 B',
+ 'USD',
+ '$',
+ '43,022.2',
+ '43,355.5',
+ '41,761.2',
+ '-253.25',
+ '-0.59',
+ '158.53 K',
+ '6.81 B',
)
self.assertEqual(a, b)
diff --git a/tests/test_api_cryptocompare.py b/tests/test_api_cryptocompare.py
index 2249f0f..9a8a702 100644
--- a/tests/test_api_cryptocompare.py
+++ b/tests/test_api_cryptocompare.py
@@ -5,14 +5,14 @@
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
import json
from unittest import TestCase
import mock
-from api import CryptoCompareClient, CoinTick, CoinInfo
+from api import CoinInfo, CoinTick, CryptoCompareClient
def _load_example(path):
@@ -24,12 +24,15 @@ def _load_example(path):
class CryptoCompareClientTest(TestCase):
def setUp(self):
- self.example_market_cap = \
- _load_example('examples/cryptocompare/mktcapfull_10.json')
- self.example_tick_prices_btc_eth = \
- _load_example('examples/cryptocompare/pricemultifull_btceth.json')
- self.example_coinlist_eth = \
- _load_example('examples/cryptocompare/coinlist_eth.json')
+ self.example_market_cap = _load_example(
+ 'examples/cryptocompare/mktcapfull_10.json'
+ )
+ self.example_tick_prices_btc_eth = _load_example(
+ 'examples/cryptocompare/pricemultifull_btceth.json'
+ )
+ self.example_coinlist_eth = _load_example(
+ 'examples/cryptocompare/coinlist_eth.json'
+ )
@mock.patch('api.web.get', autospec=True)
def test_get_tick_prices(
@@ -47,21 +50,33 @@ def test_get_tick_prices(
expected = [
CoinTick(
- 'BTC', 'Ƀ',
+ 'BTC',
+ 'Ƀ',
'https://www.cryptocompare.com/media/37746251/btc.png',
- 'USD', '$',
- '43,022.2', '43,355.5', '41,761.2',
- '-253.25', '-0.59',
- '158.53 K', '6.81 B',
+ 'USD',
+ '$',
+ '43,022.2',
+ '43,355.5',
+ '41,761.2',
+ '-253.25',
+ '-0.59',
+ '158.53 K',
+ '6.81 B',
),
CoinTick(
- 'ETH', 'Ξ',
+ 'ETH',
+ 'Ξ',
'https://www.cryptocompare.com/media/37746238/eth.png',
- 'USD', '$',
- '3,271.17', '3,313.47', '3,189.17',
- '-31.25', '-0.95',
- '1.37 M', '4.49 B',
- )
+ 'USD',
+ '$',
+ '3,271.17',
+ '3,313.47',
+ '3,189.17',
+ '-31.25',
+ '-0.95',
+ '1.37 M',
+ '4.49 B',
+ ),
]
self.assertListEqual(res, expected)
@@ -93,8 +108,10 @@ def test_get_coin_info(
res = api.get_coin_info('ETH')
expected = CoinInfo(
- 'Ethereum', 'ETH', None,
+ 'Ethereum',
+ 'ETH',
+ None,
'https://www.cryptocompare.com/media/37746238/eth.png',
- 'https://www.cryptocompare.com/coins/eth/overview'
+ 'https://www.cryptocompare.com/coins/eth/overview',
)
self.assertEqual(res, expected)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 87744d8..c898e26 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -5,7 +5,7 @@
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
from unittest import TestCase
diff --git a/utils.py b/utils.py
index 26cd5f8..2e41402 100755
--- a/utils.py
+++ b/utils.py
@@ -1,11 +1,11 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2022 Bumsoo Kim
#
# MIT Licence http://opensource.org/licenses/MIT
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
import os
import time
@@ -20,21 +20,29 @@ def create_workflow():
default_settings={
'currency': 'USD',
'favorites': [
- 'BTC', 'ETH', 'BNB', 'SOL', 'ADA',
- 'XRP', 'LUNA', 'AVAX', 'MATIC',
+ 'BTC',
+ 'ETH',
+ 'BNB',
+ 'SOL',
+ 'ADA',
+ 'XRP',
+ 'LUNA',
+ 'AVAX',
+ 'MATIC',
],
},
update_settings={
'github_slug': 'bskim45/alfred-coin-ticker',
'frequency': 7, # once a week
- })
+ },
+ )
wf.settings.save()
return wf
def clean_price_string(symbol, price_str):
- # type: (unicode, unicode) -> unicode
+ # type: (str, str) -> str
if not symbol or not price_str:
return price_str
@@ -42,7 +50,7 @@ def clean_price_string(symbol, price_str):
def get_display_change_string(change_str):
- # type: (unicode) -> unicode
+ # type: (str) -> str
if not change_str.startswith('-') and not change_str.startswith('+'):
return '📈' + change_str
elif change_str.startswith('+'):
@@ -57,7 +65,7 @@ def cached_file_age(wf, name):
if not os.path.exists(cache_path):
return 0
- return time.time() - os.stat(cache_path).st_mtime
+ return int(time.time() - os.stat(cache_path).st_mtime)
def cached_file_fresh(wf, name, max_age):
diff --git a/version b/version
old mode 100755
new mode 100644
index 1cc5f65..9084fa2
--- a/version
+++ b/version
@@ -1 +1 @@
-1.1.0
\ No newline at end of file
+1.1.0
diff --git a/workflow b/workflow
deleted file mode 120000
index d09e423..0000000
--- a/workflow
+++ /dev/null
@@ -1 +0,0 @@
-alfred-workflow/workflow
\ No newline at end of file