updating latest from mac

This commit is contained in:
2019-11-25 18:13:52 +00:00
parent 3e8988f369
commit 1c76e1801c
73 changed files with 2267 additions and 202 deletions

129
weather-cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

0
weather-cli/README.rst Normal file
View File

View File

@@ -0,0 +1,16 @@
$ complex_
complex is an example of building very complex cli
applications that load subcommands dynamically from
a plugin folder and other things.
All the commands are implemented as plugins in the
`complex.commands` package. If a python module is
placed named "cmd_foo" it will show up as "foo"
command and the `cli` object within it will be
loaded as nested Click command.
Usage:
$ pip install --editable .
$ complex --help

View File

@@ -0,0 +1,68 @@
import os
import sys
import click
CONTEXT_SETTINGS = dict(auto_envvar_prefix='COMPLEX')
class Environment(object):
def __init__(self):
self.verbose = False
self.home = os.getcwd()
def log(self, msg, *args):
"""Logs a message to stderr."""
if args:
msg %= args
click.echo(msg, file=sys.stderr)
def vlog(self, msg, *args):
"""Logs a message to stderr only if verbose is enabled."""
if self.verbose:
self.log(msg, *args)
pass_environment = click.make_pass_decorator(Environment, ensure=True)
cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__),
'commands'))
class ComplexCLI(click.MultiCommand):
def list_commands(self, ctx):
rv = []
for filename in os.listdir(cmd_folder):
if filename.endswith('.py') and \
filename.startswith('cmd_'):
rv.append(filename[4:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
try:
if sys.version_info[0] == 2:
name = name.encode('ascii', 'replace')
mod = __import__('complex.commands.cmd_' + name,
None, None, ['cli'])
except ImportError:
return
return mod.cli
@click.command(cls=ComplexCLI, context_settings=CONTEXT_SETTINGS)
@click.option('--home', type=click.Path(exists=True, file_okay=False,
resolve_path=True),
help='Changes the folder to operate on.')
@click.option('-v', '--verbose', is_flag=True,
help='Enables verbose mode.')
@pass_environment
def cli(ctx, verbose, home):
"""A complex command line interface."""
ctx.verbose = verbose
if home is not None:
ctx.home = home
if __name__ == '__main__':
cli()

View File

@@ -0,0 +1,15 @@
import click
from complex.cli import pass_environment
@click.command('init', short_help='Initializes a repo.')
@click.argument('path', required=False, type=click.Path(resolve_path=True))
@pass_environment
def cli(ctx, path):
"""Initializes a repository."""
print(f'{ctx=}')
print(f'{dir(ctx)=}')
if path is None:
path = ctx.home
ctx.log('Initialized the repository in %s',
click.format_filename(path))

View File

@@ -0,0 +1,10 @@
import click
from complex.cli import pass_environment
@click.command('status', short_help='Shows file changes.')
@pass_environment
def cli(ctx):
"""Shows file changes in the current working directory."""
ctx.log('Changed files: none')
ctx.vlog('bla bla bla, debug info')

View File

@@ -0,0 +1,16 @@
.
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-38.pyc
│ └── cli.cpython-38.pyc
├── cli.py
├── commands
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-38.pyc
│ │ ├── cmd_init.cpython-38.pyc
│ │ └── cmd_status.cpython-38.pyc
│ ├── cmd_init.py
│ ├── cmd_status.py
│ └── test
└── text

View File

@@ -0,0 +1,15 @@
from setuptools import setup
setup(
name='click-example-complex',
version='1.0',
packages=['complex', 'complex.commands'],
include_package_data=True,
install_requires=[
'click',
],
entry_points='''
[console_scripts]
complex=complex.cli:cli
''',
)

View File

@@ -0,0 +1,81 @@
https://click.palletsprojects.com/en/7.x/#documentation
https://dbader.org/blog/mastering-click-advanced-python-command-line-apps
Use click but with the poetry cleo style of importing and structuring the commands
@click.command() - creates a command out of the function
@click.argument('name') - creates an argument for the command
(e.g poetry install, install is the argument)
@click.option - creates options for the command
pass the option flags as strings, '--option', '-o'
pass a help string , help='help string'
specify to use an os env , envvar="API_KEY"
https://click.palletsprojects.com/en/5.x/options/
- use / in an option to set a flag as true of false
setting arguments: https://click.palletsprojects.com/en/5.x/arguments/#arguments
https://github.com/pallets/click/tree/master/examples/complex/complex
for a good example on structure using click in a module
Callbacks and eager options to implement flags such as a version
https://click.palletsprojects.com/en/5.x/options/#callbacks-and-eager-options
Can use
@click.version_option(version=version)
to implement a version
Make pass decorator explained:
https://stackoverflow.com/questions/49511933/better-usage-of-make-pass-decorator-in-python-click
Show how to run command line programs from within a script from the example above ^^
For complex example:
We use the class Environment to store any base arguments passed (e.g a path)
This is optional, but is useful if we need an argument initially and want to do something to use it later in another subcommand.
This gets passed as a decorator to each command we create
We can then use this class to retrieve the inputs in each command in each module
We use this class to also handle logging, using click.echo() to print things if needed.
We avoid using the group functionality in click, by using a custom class that inherits from click.MultiCommand.
We can use this class to override the list_commands() and get_command() methods to list and do the commands we write.
Each one of those commands can go in their own folder, be decorated with the Environment class and have their commands passed
ctx refers to the instance of the class we passed in with the decorator when using click.make_pass_decorator()
when using this decorator, the first argument to the function will refer to the instanced class we passed in
we can also import defaults which we can store in a class
say we have a class that reads default values from a config file locally. it also sets default values without a config file. we store all of this in a class using properties.
when creating an option we can use this class to fall back on if the option isnt passed to the command. to do this we create a subclass of click.Option, and use 2 closures.
2 closures are needed because we pass in 2 things: the settings instance class and the value of the option itself.
this allows to use the class attributes when creating options, and also allows us access to attributes when writing the commands, without needing two seperate instances
we could avoid doing this, by following the complex example. here there is one class which is passed in, but the default aren't set in click. instead the defauts are set in a class and passed in with the decorator.
we can have a class for each command that needs it, and we set the defaults by putting a if option is None in the command/subcommand itself, and setting the value to the class value if it's not provided.
if we do it this way, we would need a class for each command that needs it, and pass it in. or we could have one class, and use the method above to do it.

View File

@@ -0,0 +1,87 @@
import click
import sys
import time
def build_settings_option_class(settings_instance):
def set_default(default_name):
class Cls(click.Option):
def __init__(self, *args, **kwargs):
kwargs['default'] = getattr(settings_instance, default_name)
super(Cls, self).__init__(*args, **kwargs)
def handle_parse_result(self, ctx, opts, args):
obj = ctx.find_object(type(settings_instance))
if obj is None:
ctx.obj = settings_instance
return super(Cls, self).handle_parse_result(ctx, opts, args)
return Cls
return set_default
class Settings(object):
def __init__(self):
self.instance_disk_size = 100
self.instance_disk_type = 'pd-ssd'
# import pudb; pudb.set_trace()
settings = Settings()
settings_option_cls = build_settings_option_class(settings)
pass_settings = click.make_pass_decorator(Settings)
@click.command()
@click.help_option('-h', '--help')
@click.option(
'-s',
'--disk-size',
cls=settings_option_cls('instance_disk_size'),
help="Disk size",
show_default=True,
type=int,
)
@click.option(
'-t',
'--disk-type',
cls=settings_option_cls('instance_disk_type'),
help="Disk type",
show_default=True,
type=click.Choice(['pd-standard', 'pd-ssd']),
)
@pass_settings
def create(settings_test, disk_size, disk_type):
print(f'{settings_test.instance_disk_type=}')
# print(f'{dir(settings_test)=}')
print(disk_size)
print(disk_type)
if __name__ == "__main__":
commands = (
'-t pd-standard -s 200',
'-t pd-standard',
'-s 200',
'',
'--help',
)
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
create(cmd.split())
except BaseException as exc:
if str(exc) != '0' and not isinstance(
exc, (click.ClickException, SystemExit)
):
raise

179
weather-cli/poetry.lock generated Normal file
View File

@@ -0,0 +1,179 @@
[[package]]
category = "dev"
description = "Atomic file writes."
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.0"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
[[package]]
category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false
python-versions = "*"
version = "2019.9.11"
[[package]]
category = "main"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
optional = false
python-versions = "*"
version = "3.0.4"
[[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "7.0"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1"
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.8"
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.4"
version = "7.2.0"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[[package]]
category = "dev"
description = "A full-screen, console-based Python debugger"
name = "pudb"
optional = false
python-versions = "*"
version = "2019.2"
[package.dependencies]
pygments = ">=1.0"
urwid = ">=1.1.1"
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.8.0"
[[package]]
category = "dev"
description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.4.2"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.10.1"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
pluggy = ">=0.7"
py = ">=1.5.0"
setuptools = "*"
six = ">=1.10.0"
[[package]]
category = "main"
description = "Python HTTP for Humans."
name = "requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.22.0"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<3.1.0"
idna = ">=2.5,<2.9"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[[package]]
category = "dev"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "1.13.0"
[[package]]
category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.25.7"
[[package]]
category = "dev"
description = "A full-featured console (xterm et al.) user interface library"
name = "urwid"
optional = false
python-versions = "*"
version = "2.1.0"
[metadata]
content-hash = "a616bd3aec3b75e8103ccaa821e9b7029caa70647f38a2cf7e492f651aa3772c"
python-versions = "^3.8"
[metadata.hashes]
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
certifi = ["e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"]
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
pluggy = ["15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"]
pudb = ["e8f0ea01b134d802872184b05bffc82af29a1eb2f9374a277434b932d68f58dc"]
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"]
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"]
urllib3 = ["a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"]
urwid = ["0896f36060beb6bf3801cb554303fef336a79661401797551ba106d23ab4cd86"]

View File

@@ -0,0 +1,18 @@
[tool.poetry]
name = "weather-cli"
version = "0.1.0"
description = ""
authors = ["dtomlinson <dtomlinson@panaetius.co.uk>"]
[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.22"
click = "^7.0"
[tool.poetry.dev-dependencies]
pytest = "^3.0"
pudb = "^2019.2"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

View File

@@ -0,0 +1,13 @@
from .__version__ import __version__ # noqa
import requests
def current_weather(location, api_key):
url = 'https://api.openweathermap.org/data/2.5/weather'
query_params = {
'q': location,
'appid': api_key,
}
response = requests.get(url, params=query_params)
return response.json()['weather'][0]['description']

View File

@@ -0,0 +1 @@
__version__ = '0.1.0'

View File

@@ -0,0 +1,27 @@
import weather_cli
import click
@click.group()
@click.version_option(version=weather_cli.__version__)
def main(version):
pass
@main.command()
@click.argument('location')
@click.option(
'--api-key', '-a', help='your API key for the OpenWeatherMap API'
)
def current(location: str, api_key: str):
"""Query the openWeather API for the current weather conditions
"""
# import pudb; pudb.set_trace()
if api_key is None:
api_key = weather_cli.SAMPLE_API_KEY
weather = weather_cli.current_weather(location=location, api_key=api_key)
print(f'The weather in {location} is {weather}')
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
import weather_cli
london = weather_cli.current_weather(
'Leeds', 'ed0172b84ddf4e63a2860957644f91fb'
)
print(london)