Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
Technology
0
220
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
Tweet
Share
More Decks by Seb
See All by Seb
I Can Be A Speaker, So Can You
elbaschid
0
170
Click - PyCaribbean 2017 - Puerto Rico
elbaschid
0
210
Conferencing - Engineering Meeting
elbaschid
1
32
Show & Tell - PyCon US 2016 Summary
elbaschid
1
40
Click: A Pleasure To Write, A Pleasure To Use
elbaschid
0
370
Hunting for Treasure in Django
elbaschid
1
410
Moby & The Beanstalk
elbaschid
1
310
Docker In Production - A War Story
elbaschid
1
280
Hunting For Treasure In Django
elbaschid
0
120
Other Decks in Technology
See All in Technology
Bill One 開発エンジニア 紹介資料
sansantech
PRO
0
120
Dockerに疲れた人のためのLXDではじめるシステムコンテナ入門
devops_vtj
0
130
スクラム導入して変わったチーム、組織のありかた
yumechi
0
220
SPA・SSGでSSRのようなOGP対応!
simo123
2
160
スクラムマスターの悩みどころを赤裸々に告白します
nagata03
0
120
OpenShiftでスポットVMを使おう.pdf
jpishikawa
1
400
IoTを始めたきっかけの話と個人でできるIoTの今後 / 新年LT会「私の愛するIoT 2023」
you
0
250
証明書って何だっけ? 〜AWSの中間CA移行に備える〜
minorun365
3
2.1k
USB PD で迎える AC アダプター大統一時代
puhitaku
2
2k
re:Inventの完全招待制イベント Building a Roadmap to SaaSについて / Building a Roadmap to SaaS an invitation only event at reinvent
yayoi_dd
0
150
OCIコンテナサービス関連の技術詳細 /oke-ocir-details
oracle4engineer
PRO
0
780
DNS権威サーバのクラウドサービス向けに行われた攻撃および対策 / DNS Pseudo-Random Subdomain Attack and mitigations
kazeburo
5
1.3k
Featured
See All Featured
5 minutes of I Can Smell Your CMS
philhawksworth
198
18k
BBQ
matthewcrist
75
8.1k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
657
120k
Building Applications with DynamoDB
mza
85
5k
What's in a price? How to price your products and services
michaelherold
233
9.7k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
217
21k
Music & Morning Musume
bryan
37
4.6k
Making the Leap to Tech Lead
cromwellryan
117
7.7k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
224
50k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
44
14k
How to train your dragon (web standard)
notwaldorf
66
4.3k
Imperfection Machines: The Place of Print at Facebook
scottboms
254
12k
Transcript
Double Click Continue Building Better CLIs Sebastian Vetter @elbaschid Slides:
bit.ly/pyconca-double-click
Check Out Click - Part I Click - A Pleasure
To Write, A Pleasure To Use (PyCon US 2016)
Seb • @elbaschid • Freelance Coder at Roadside Software •
Outdoor Tour Guide • Vancouver ➡ Rocky Mountains
Why This Talk?
Terminology
Parameter • Argument • Option
Argument • Mandatory parameter • Only values required What It
Looks Like: $ pgcli postgresql://....
Option • Optional parameter • Name and value required •
Flags as special options What It Looks Like: $ heroku --help $ heroku logs --app my-heroku-app
(Sub-)Command • Nested commands allowed • Groups sub-commands • Has
options & arguments What It Looks Like: $ pip install django
Introducing Click
click • Author: Armin Ronacher • Version 6.x • http://click.pocoo.org/6/
• Why Click?
Let's Use It
pip install click
Basic Example # cli.py import click @click.command() @click.option('--times', '-t', type=int)
@click.argument('symbol') def main(times, symbol): print(f'I {symbol * times} Click!') if __name__ == '__main__': main()
Running It $ python test.py -t 5 ❤ I ❤❤❤❤❤
Click!
Breaking It $ python test.py -t five ❤ Usage: test.py
[OPTIONS] SYMBOL Error: Invalid value for "--times" / "-t": five is not a valid integer
Predict The Weather
Using A Web API
Github Repos • Click Template: bit.ly/click-template • Example Code: bit.ly/double-click-example
We Are Building • weather config • weather forecast LOCATION
weather config $ weather config Please enter your API key
[]: mysecretapikey $ cat ~/.weather.cfg mysecretapikey
weather forecast $ weather forecast Montreal Time Description Min Temp
Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ...
Multiple Commands
Group import click @click.command() def main(): pass
Group import click @click.group() def main(): pass
config Command @click.command() def config(): pass
config Command @main.command() def config(): pass
forecast Command @main.command() @click.argument('location') def forecast(location): pass
Store The API Key
Running It $ weather config Please enter your API key
[]: mysecretapikey $ cat ~/.weather.cfg mysecretapikey
Parameter Types
Basic Types Click Types int UUID float File str Path
bool Choice IntRange
Using The Path Type @main.option( '--config-file', type=click.Path(), default=os.path.expanduser('~/.weather.cfg')) @click.option('--api-key', envvar='API_KEY',
default='') def config(config_file, api_key): ...
Using The Path Type @main.option( '--config-file', type=click.Path(exists=True, writable=True), default=os.path.expanduser('~/.weather.cfg')) @click.option('--api-key',
envvar='API_KEY', default='') def config(config_file, api_key): ...
Save The Key def config(config_file, api_key): api_key = click.prompt( 'Please
enter your API key', default=api_key ) with open(config_file, 'w') as cfg: cfg.write(api_key)
Look At The Weather
weather forecast <name> $ weather forecast Montreal Time Description Min
Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ... API Call https://api.openweathermap.org/data/2.5/forecast?q=Montreal
weather forecast <city ID> $ weather forecast 6077243 Time Description
Min Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ... API Call https://api.openweathermap.org/data/2.5/forecast?id=6077243
Custom Parameter Type
The Return Type from collections import namedtuple Location = namedtuple('Location',
['query', 'value'])
Our Location Type class LocationType(click.ParamType): name = 'location' def convert(self,
value, param, ctx): try: value = int(value) except ValueError: query = 'q' else: query = 'id' return Location(query, value)
Use It @main.command() @click.argument('location', type=LocationType()) def forecast(location): ... url =
'https://api.openweathermap.org/data/2.5/forecast' params = { location.query: location.value, } response = session.get(url, params=params) ...
weather forecast $ weather forecast Montreal Time Description Min Temp
Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ...
Don't Break It
The Test Runner from click.testing import CliRunner def test_writing_config_file(): runner
= CliRunner() runner.invoke( main, ['config'], input='mysecretapikey', ) ...
Filesystem Tests with runner.isolated_filesystem() as env: api_key = '00ee52f44f3350c73f2684a0f23f2805' filename
= f'{env}/weather.cfg' result = runner.invoke( main, ['--config-file', filename, 'config'], input=api_key, )
The Result assert result.exit_code == 0 assert api_key in result.output
with open(filename) as cfg_file: assert cfg_file.read() == api_key
Packaging Your CLI
Learn More • PyPA Packaging User Guide • Grug make
fire! Grug make wheel! - Russell Keith-Magee • Shipping Software To Users With Python - Glyph
Choose A License It's a minefield, people. All I'm saying
is this: the next time you release code into the wild, do your fellow developers a favor and pick a license – any license. — Coding Horror
Start With Here # setup.py setup( name='double-click-weather', version='0.0.1', license='MIT', ...
)
Create The Executable # setup.py setup( ... entry_points={'console_scripts': [ 'weather
= forecast.simple:main']}, ... )
pip install double-click-weather
File Location ${VENV}/bin/weather.py
Generated Code #!${VENV}/bin/python3 # EASY-INSTALL-ENTRY-SCRIPT: 'double-click-weather','console_scripts','weather' __requires__ = 'double-click-weather' import
re import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit( load_entry_point('double-click-weather', 'console_scripts', 'weather')() )
You Have Many New Tools
PyCascades 2018 Vancouver, BC ! 22-23 January, 2018 ! Get
Your Ticket ! http://www.pycascades.com
None
Need Help Building Cool Let's Talk
[email protected]
| www.roadsi.de Slides:
bit.ly/pyconca-double-click Code: bit.ly/double-click-example
Chains & Pipes
Unix-style Pipeline $ weather find Montreal | weather forecast Time
Description Min Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 ... ... ... ...
Chainable Group @click.group(chain=True) def main(): pass
Write To stdout @main.command() @click.argument('location', type=LocationType()) @click.argument('output', type=click.File('w'), default='-') def
find(location, output): ... output.write(f"{location.value}")
Read From stdin @main.command() @click.argument('location', type=click.File('r')) def forecast(location): value =
location.read() ...