Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Double Click - Continue Building Better CLIs
Search
Seb
November 18, 2017
Technology
510
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
More Decks by Seb
See All by Seb
I Can Be A Speaker, So Can You
elbaschid
0
340
Click - PyCaribbean 2017 - Puerto Rico
elbaschid
0
480
Conferencing - Engineering Meeting
elbaschid
1
55
Show & Tell - PyCon US 2016 Summary
elbaschid
1
110
Click: A Pleasure To Write, A Pleasure To Use
elbaschid
0
700
Hunting for Treasure in Django
elbaschid
1
750
Moby & The Beanstalk
elbaschid
1
560
Docker In Production - A War Story
elbaschid
1
320
Hunting For Treasure In Django
elbaschid
0
190
Other Decks in Technology
See All in Technology
V8のArray.prototype.flatを最大約20倍速くするまでと、巨大OSSへの大規模コミットの道のり
riyaamemiya
0
230
AI時代のPRレビューにどう向き合うか?
kairim0
1
170
[AWS 秋のクラウドオペレーション祭り 2026]AWS DevOps Agentで変わるリリースと運用対応 ~リリース管理機能と Directed actions のご紹介~
furuton
4
1.2k
実践!Cloud Run ハンズオン!! / Cloud Run handson
y0hgi
0
200
12 個 Tool と、 4,096 Token
hlum
0
130
WasmをRustにする
sansantech
PRO
0
150
More Freedom on the Same Shared GPU Cluster: A Small Team’s Experience with vCluster
nttcom
0
150
ai_cording_with_k8s_knowledge.pdf
mochizuki875
1
330
LocalStack を使ったサーバーレスアプリケーション開発 / Serverless Development with LocalStack
kakakakakku
1
390
AI時代に必要な脅威モデリング― 変化の速度に負けない理解とリスク判断
masakane55
5
1.3k
DGX Spark でどこまでハッキングができるのか?
kash1064
0
280
AIに攻撃される前に、AIに攻撃させる
tsuchikazu
0
170
Featured
See All Featured
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
590
Testing 201, or: Great Expectations
jmmastey
46
8.3k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
280
How to Think Like a Performance Engineer
csswizardry
28
2.9k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
WCS-LA-2024
lcolladotor
0
870
RailsConf 2023
tenderlove
30
1.6k
Test your architecture with Archunit
thirion
2
2.4k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
610
The SEO identity crisis: Don't let AI make you average
varn
0
580
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.6k
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() ...