Python Sanic ext 22.9.0 - Open API static YAML file loading - python

I am using sanic/sanic-ext 22.9.0 , I need to load a static open api yaml or json file rather than autogenerate so that when I access it as <url>/docs, the static yaml file is loaded and UI display either Swagger or redoc version of UI with the API definition.
Any suggestion on how to achieve this.

You have a few options.
OPTION 1
Load your custom OAS into Sanic, and server that.
#app.before_server_start
async def load_oas(app: Sanic):
custom_oas_dict = load_spec_from_yaml()
app.ext.openapi.raw(custom_oas_dict)
OPTION 2
Turn off OAS and roll your own solution, including swagger/redoc, etc
app.config.OAS = False
OPTION 3
Serve your custom OAS, and overwrite the HTML to point to your custom file.
app.config.OAS_PATH_TO_REDOC_HTML = "/path/to/custom/redoc.html"
app.config.OAS_PATH_TO_SWAGGER_HTML = "/path/to/custom/swagger.html"
app.static("/custom/oas.json", "/path/to/custom/oas.json")

Related

Flask autoescaping for files with uncommon extentions

I found that in flask >= 0.5 there is autoescaping enabled only for the little amount of extensions not including j2. I use j2 extentions for my templates to get coloring and another features from my editor. So how can I enable autoescaping for certain file extentions in Flask in the most simple and convenient way?
See http://jinja.pocoo.org/docs/2.10/api/#autoescaping
To enable it globally you can use :
from jinja2 import select_autoescape
app.jinja_env.autoescape = select_autoescape(
default_for_string=True,
default=True
)
Or for specified extensions :
app.jinja_env.autoescape = select_autoescape(
default_for_string=True,
enabled_extensions=('html', 'xml', 'j2')
)

Routing static pages with Flask or Bottle

When using:
#route('/<filename>')
def server_static(filename):
return static_file(filename, root='.')
it allows to serve the request www.example.com/helloworld with the static HTML file /myapp/helloworld.
How to make www.example.com/anything be served by the static HTML file /myapp/html/anything.html, without having to hardcode each static filename anything in the Python code?
Note: the tricky part is that the request is /anything (and not /anything.html), and the static file is /myapp/html/anything.html (there are 20 or more such files)
If it's always .html you could consider simply doing filename += '.html'.
If your needs are more complex, you could write code that examines the directory to match various extensions. For example, if you wanted to match .html files, something like this would work and would be adjustable to work with various/multiple extensions or other conditions.
def is_html(filename):
return filename.lower().endswith('.html')
#route('/<filename>')
def server_static(filename):
root = '.'
all_filenames = os.listdir(root)
html_files = filter(is_html, all_filenames)
for fname in html_files:
if fname.startswith(filename):
return static_file(fname, root=root)
else:
return "No such file"
This can probably be abbreviated using fnmatch or something similar.
Although, you may want to simply consider simply having a webserver like Apache simply serve that path instead of using Flask. This would probably be a more secure option, too.

With Python Kubernetes client, how to replicate `kubectl create -f` generally?

My Bash script using kubectl create/apply -f ... to deploy lots of Kubernetes resources has grown too large for Bash. I'm converting it to Python using the PyPI kubernetes package.
Is there a generic way to create resources given the YAML manifest? Otherwise, the only way I can see to do it would be to create and maintain a mapping from Kind to API method create_namespaced_<kind>. That seems tedious and error prone to me.
Update: I'm deploying many (10-20) resources to many (10+) GKE clusters.
Update in the year 2020, for anyone still interested in this (since the docs for the python library is mostly empty).
At the end of 2018 this pull request has been merged,
so it's now possible to do:
from kubernetes import client, config
from kubernetes import utils
config.load_kube_config()
api = client.ApiClient()
file_path = ... # A path to a deployment file
namespace = 'default'
utils.create_from_yaml(api, file_path, namespace=namespace)
EDIT: from a request in a comment, a snippet for skipping the python error if the deployment already exists
from kubernetes import client, config
from kubernetes import utils
config.load_kube_config()
api = client.ApiClient()
def skip_if_already_exists(e):
import json
# found in https://github.com/kubernetes-client/python/blob/master/kubernetes/utils/create_from_yaml.py#L165
info = json.loads(e.api_exceptions[0].body)
if info.get('reason').lower() == 'alreadyexists':
pass
else
raise e
file_path = ... # A path to a deployment file
namespace = 'default'
try:
utils.create_from_yaml(api, file_path, namespace=namespace)
except utils.FailToCreateError as e:
skip_if_already_exists(e)
I have written a following piece of code to achieve the functionality of creating k8s resources from its json/yaml file:
def create_from_yaml(yaml_file):
"""
:param yaml_file:
:return:
"""
yaml_object = yaml.loads(common.load_file(yaml_file))
group, _, version = yaml_object["apiVersion"].partition("/")
if version == "":
version = group
group = "core"
group = "".join(group.split(".k8s.io,1"))
func_to_call = "{0}{1}Api".format(group.capitalize(), version.capitalize())
k8s_api = getattr(client, func_to_call)()
kind = yaml_object["kind"]
kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
if "namespace" in yaml_object["metadata"]:
namespace = yaml_object["metadata"]["namespace"]
else:
namespace = "default"
try:
if hasattr(k8s_api, "create_namespaced_{0}".format(kind)):
resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))(
body=yaml_object, namespace=namespace)
else:
resp = getattr(k8s_api, "create_{0}".format(kind))(
body=yaml_object)
except Exception as e:
raise e
print("{0} created. status='{1}'".format(kind, str(resp.status)))
return k8s_api
In above function, If you provide any object yaml/json file, it will automatically pick up the API type and object type and create the object like statefulset, deployment, service etc.
PS: The above code doesn't handler multiple kubernetes resources in one file, so you should have only one object per yaml file.
I see what you are looking for. This is possible with other k8s clients available in other languages. Here is an example in java. Unfortunately the python client library does not support that functionality yet. I opened a new feature request requesting the same and you can either choose to track it or contribute yourself :). Here is the link for the issue on GitHub.
The other way to still do what you are trying to do is to use java/golang client and put your code in a docker container.

Autoprefixer Filter Not Working in Flask_Assets

I have tried to get the autoprefixer filter to work with flask_assets by following the instructions in the Flask_Assets documentation, but it does not appear to apply the filter. Here is my code:
# construct flask app object
from flask import Flask, render_template_string
flask_args = { 'import_name': __name__ }
flask_app = Flask(**flask_args)
from flask_assets import Environment, Bundle
assets = Environment(flask_app)
assets.config['AUTOPREFIXER_BIN'] = 'postcss'
assets.config['AUTOPREFIXER_BROWSERS'] = [ '> 1%' ]
css_min = Bundle('../styles/mycss.css', filters='autoprefixer', output='styles/test.css')
assets.register('css_assets', css_min)
#flask_app.route('/')
def landing_page():
html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\
<head>{% assets "css_assets" %}\
<link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css">\
{% endassets %}\
<title>Hello</title>\
</head>\
<h1>Hello World</h1>\
<p>Just a test of flask</p>'
return render_template_string(html), 200
if __name__ == '__main__':
flask_app.run(host='0.0.0.0', port=5000)
I have been able to apply the cssmin, pyscss, uglifyjs and jsmin filters successfully. I can also run autoprefixer on the command line to successfully compile a transformed output:
postcss --use autoprefixer --autoprefixer.browsers "> 1%" -o test.css mycss.css
However, when trying to run autoprefixer through flask_assets registration, the process neither throws an error nor does it seem to take the required time to compile. It does produce the output file but when I examine the resulting file, none of the prefixes have been applied.
UPDATE: This problem seems to occur whenever attempting to configure options for ANY filter. I have not been able to get uglifyjs to accept 'UGLIFYJS_EXTRA_ARGS' or for the pyscss filter to adopt a new style using 'PYSCSS_STYLE' either. I have tried to set these configuration as environmental variables using os.environ['AUTOPREFIXER_BIN'] as well as attempting to pass them through flask.config['AUTOPREFIXER_BIN']. But none of the configuration settings have been applied when the filter is run. It is also not clear to me where in the code itself the configuration options are constructed by either Bundle or Environment.
One SO post claims to have found a way to get a configuration setting to work, but the post does not show the entire workflow of how flask_assets needs to be setup to ingest these options.
Perhaps someone can help me understand what I am doing wrong?
Autoprefixer:
There is nothing wrong with your code1. You are just not using the correct filter for the latest version of Autoprefixer. If you look at the history of the releases in that link, since version 6.0.0, it started using postcss. Your code will work for versions older than 6.0.0.
Webassets has provided support for versions after 6.0.0 (inclusive), by providing the autoprefixer6 filter.
Therefore all you have to do is change the filter(s) while initializing your bundle, like so:
css_min = Bundle('../styles/mycss.css', filters='autoprefixer6', output='styles/test.css')
Other Filters' Configurations:
Don't use os.environ, that is not the way to set configuration variables for Flask and flask-extensions. The most common (and preferred) way to set configuration for extensions is by using the flask Config itself, and in large projects this is done using a separate config file. The extensions will pickup its configuration options from flask's config.
Depending on which extension you use, you can also set the config separately like you have done, but that is rarely used, from what I have seen so far.
Please check the Flask's Configuration related documentation for some good examples on how to setup configuration for your app "properly".
from flask import Flask, render_template_string
from flask_assets import Environment, Bundle
# construct flask app object
flask_args = {'import_name': __name__}
flask_app = Flask(**flask_args)
assets = Environment(flask_app)
# specify the bin path (optional), required only if not globally installed
assets.config['AUTOPREFIXER_BIN'] = 'path/to/postcss'
assets.config['AUTOPREFIXER_BROWSERS'] = ['> 1%', ]
# use the autoprefixer6 updated filter
css_min = Bundle('../styles/mycss.css', filters='autoprefixer6',
output='styles/test.css')
assets.register('css_assets', css_min)
#flask_app.route('/')
def landing_page():
html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\
<head>{% assets "css_assets" %}\
<link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css">\
{% endassets %}\
<title>Hello</title>\
</head>\
<h1>Hello World</h1>\
<p>Just a test of flask</p>'
return render_template_string(html), 200
if __name__ == '__main__':
flask_app.run(host='0.0.0.0', port=5000)
Remember to clean out the previously generated files, if the source css/js has not changed, i.e remove the output files, and the .webassets-cache folder.
1Except for code style & formatting conventions!

django-pipeline with s3 storage is not compressing my js

I'm using django-pipeline with s3. I'm successfully using collectstatic to combined my Javascript files and store them in my s3 bucket, but they are not getting compressed for some reason (verified by looking at the file, its size, and its content-encoding). Otherwise things are working correctly with the combined scripts.js that is produced.
Here are the changes I made to use django-pipeline:
Added pipeline to installed apps.
Added 'pipeline.finders.PipelineFinder' to STATICFILES_FINDERS.
Set STATICFILES_STORAGE = 'mysite.custom_storages.S3PipelineManifestStorage' where this class is as defined in the documentation, as seen below.
Set PIPELINE_JS as seen below, which works but just isn't compressed.
PIPELINE_ENABLED = True since DEBUG = True and I'm running locally.
PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.yuglify.YuglifyCompressor' even though this should be default.
Installed the Yuglify Compressor with npm -g install yuglify.
PIPELINE_YUGLIFY_BINARY = '/usr/local/bin/yuglify' even though the default with env should work.
Using the {% load pipeline %} and {% javascript 'scripts' %} which work.
More detail:
PIPELINE_JS = {
'scripts': {
'source_filenames': (
'lib/jquery-1.11.1.min.js',
...
),
'output_filename': 'lib/scripts.js',
}
}
class S3PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, S3BotoStorage):
location = settings.STATICFILES_LOCATION
As mentioned, collectstatic does produce scripts.js just not compressed. The output of that command includes:
Post-processed 'lib/scripts.js' as 'lib/scripts.js'
I'm using Django 1.8, django-pipeline 1.5.2, and django-storages 1.1.8.
Similar questions:
django-pipeline not compressing
django pipeline with S3 storage not compressing
The missing step was to also extend GZipMixin, AND, it has to be first in the list of parents:
from pipeline.storage import GZIPMixin
class S3PipelineManifestStorage(GZIPMixin, PipelineMixin, ManifestFilesMixin, S3BotoStorage):
location = settings.STATICFILES_LOCATION
Now collectstatic produces a .gz version of each file as well, but my templates still weren't referencing the .gz version.
To address this the author says:
To make it work with S3, you would need to change the staticfiles
storage url method to return .gz urls (and staticfiles/pipeline
template tags depending if you care for clients that don't support
gzip). Also don't forget to setup the proper header on s3 to serve
theses assets as being gzipped.
I adapted an example he provided elsewhere, which overrides the url method:
class S3PipelineManifestStorage(GZIPMixin, PipelineMixin, ManifestFilesMixin, S3BotoStorage):
location = settings.STATICFILES_LOCATION
def url(self, name, force=False):
# Add *.css if you are compressing those as well.
gzip_patterns = ("*.js",)
url = super(GZIPMixin, self).url(name, force)
if matches_patterns(name, gzip_patterns):
return "{0}.gz".format(url)
return url
This still doesn't handle setting the Content-Encoding header.
A simpler alternative is to use the S3Boto Storages option AWS_IS_GZIPPED which performs gzipping AND sets the appropriate header.
More is required to support clients without gzip, however.
Also useful are these instructions from Amazon on serving compressed files from S3.

Categories

Resources