Commit 0d920a77 authored by Mark Beierl's avatar Mark Beierl
Browse files

Change srsLTE to Full Native Charm



Changes the charm from relying on a prepackaged image to
building srsue and srsenb from tagged source.
Updates the descriptor to use generic name for relation.
Signed-off-by: Mark Beierl's avatarbeierlm <mark.beierl@canonical.com>
parent 8bc30d96
Pipeline #71 passed with stage
in 1 minute and 22 seconds
#!/usr/bin/env python3
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.
import logging
import os
import shutil
from ops.charm import CharmBase
from ops.main import main
from ops.framework import StoredState
from ops.model import (
MaintenanceStatus,
ActiveStatus,
# BlockedStatus,
)
from jinja2 import Template
# from typing import Dict, Any
from utils import (
service_active,
service_start,
service_stop,
service_restart,
service_enable,
systemctl_daemon_reload,
install_apt,
git_clone,
shell,
copy_files,
is_ipv4,
ip_from_default_iface,
ip_from_iface,
)
logger = logging.getLogger(__name__)
APT_REQUIREMENTS = [
"git",
"libzmq3-dev",
"cmake",
"build-essential",
"libmbedtls-dev",
"libboost-program-options-dev",
"libsctp-dev",
"libconfig++-dev",
"libfftw3-dev",
"net-tools",
]
GIT_REPO = "https://github.com/srsLTE/srsLTE.git"
GIT_REPO_TAG = "release_20_10"
SRC_PATH = "/srsLTE"
BUILD_PATH = "/build"
CONFIG_PATH = "/config"
SERVICE_PATH = "/service"
CONFIG_PATHS = {
"enb": f"{CONFIG_PATH}/enb.conf",
"drb": f"{CONFIG_PATH}/drb.conf",
"rr": f"{CONFIG_PATH}/rr.conf",
"sib": f"{CONFIG_PATH}/sib.conf",
"sib.mbsfn": f"{CONFIG_PATH}/sib.mbsfn.conf",
"ue": f"{CONFIG_PATH}/ue.conf",
}
CONFIG_ORIGIN_PATHS = {
"enb": f"{SRC_PATH}/srsenb/enb.conf.example",
"drb": f"{SRC_PATH}/srsenb/drb.conf.example",
"rr": f"{SRC_PATH}/srsenb/rr.conf.example",
"sib": f"{SRC_PATH}/srsenb/sib.conf.example",
"sib.mbsfn": f"{SRC_PATH}/srsenb/sib.conf.mbsfn.example",
"ue": f"{SRC_PATH}/srsue/ue.conf.example",
}
SRS_ENB_SERVICE = "srsenb"
SRS_ENB_BINARY = f"{BUILD_PATH}/srsenb/src/srsenb"
SRS_ENB_SERVICE_TEMPLATE = "./templates/srsenb.service"
SRS_ENB_SERVICE_PATH = "/etc/systemd/system/srsenb.service"
SRS_UE_SERVICE = "srsue"
SRS_UE_BINARY = f"{BUILD_PATH}/srsue/src/srsue"
SRS_UE_SERVICE_TEMPLATE = "./templates/srsue.service"
SRS_UE_SERVICE_PATH = "/etc/systemd/system/srsue.service"
SRS_ENB_UE_BUILD_COMMAND = (
f"cd {BUILD_PATH} && cmake {SRC_PATH} && make -j `nproc` srsenb srsue"
)
class SrsLteCharm(CharmBase):
_stored = StoredState()
def __init__(self, *args):
super().__init__(*args)
self._stored.set_default(
mme_addr=None,
bind_addr=None,
ue_usim_imsi=None,
ue_usim_k=None,
ue_usim_opc=None,
installed=False,
started=False,
ue_attached=False,
)
# Basic hooks
self.framework.observe(self.on.install, self._on_install)
self.framework.observe(self.on.start, self._on_start)
self.framework.observe(self.on.stop, self._on_stop)
self.framework.observe(self.on.config_changed, self._on_config_changed)
self.framework.observe(self.on.update_status, self._on_update_status)
# Actions hooks
self.framework.observe(self.on.attach_ue_action, self._on_attach_ue_action)
self.framework.observe(self.on.detach_ue_action, self._on_detach_ue_action)
self.framework.observe(
self.on.remove_default_gw_action, self._on_remove_default_gw_action
)
# Relations hooks
self.framework.observe(self.on.mme_relation_changed, self._mme_relation_changed)
# Basic hooks
def _on_install(self, _):
self.unit.status = MaintenanceStatus("Installing apt packages")
install_apt(packages=APT_REQUIREMENTS, update=True)
self.unit.status = MaintenanceStatus("Preparing the environment")
self._reset_environment()
self.unit.status = MaintenanceStatus("Downloading srsLTE from Github")
git_clone(GIT_REPO, output_folder=SRC_PATH, branch=GIT_REPO_TAG, depth=1)
self.unit.status = MaintenanceStatus("Building srsLTE")
shell(SRS_ENB_UE_BUILD_COMMAND)
self.unit.status = MaintenanceStatus("Generating configuration files")
copy_files(origin=CONFIG_ORIGIN_PATHS, destination=CONFIG_PATHS)
self.unit.status = MaintenanceStatus("Generating systemd files")
self._configure_srsenb_service()
self._configure_srsue_service()
service_enable(SRS_ENB_SERVICE)
self._stored.installed = True
def _on_start(self, _):
self.unit.status = MaintenanceStatus("Starting srsenb")
service_start(SRS_ENB_SERVICE)
self._stored.started = True
self.unit.status = self._get_current_status()
def _on_stop(self, _):
self._reset_environment()
service_stop(SRS_ENB_SERVICE)
self._stored.started = False
self.unit.status = self._get_current_status()
def _on_config_changed(self, _):
self._stored.bind_addr = self._get_bind_address()
self._configure_srsenb_service()
# Restart the service only if it is running
if self._stored.started:
self.unit.status = MaintenanceStatus("Reloading srsenb")
service_restart(SRS_ENB_SERVICE)
self.unit.status = self._get_current_status()
def _on_update_status(self, _):
self.unit.status = self._get_current_status()
# Action hooks
def _on_attach_ue_action(self, event):
self._stored.ue_usim_imsi = event.params["usim-imsi"]
self._stored.ue_usim_k = event.params["usim-k"]
self._stored.ue_usim_opc = event.params["usim-opc"]
self._configure_srsue_service()
service_restart(SRS_UE_SERVICE)
self._stored.ue_attached = True
self.unit.status = self._get_current_status()
event.set_results({"status": "ok", "message": "Attached successfully"})
def _on_detach_ue_action(self, event):
self._stored.ue_usim_imsi = None
self._stored.ue_usim_k = None
self._stored.ue_usim_opc = None
service_stop(SRS_UE_SERVICE)
self._configure_srsue_service()
self._stored.ue_attached = False
self.unit.status = self._get_current_status()
event.set_results({"status": "ok", "message": "Detached successfully"})
def _on_remove_default_gw_action(self, event):
shell("route del default")
event.set_results({"status": "ok", "message": "Default route removed!"})
# Relation hooks
def _mme_relation_changed(self, event):
# Get mme address from relation
if event.unit in event.relation.data:
mme_addr = event.relation.data[event.unit].get("mme-addr")
if not is_ipv4(mme_addr):
return
self._stored.mme_addr = mme_addr
self._configure_srsenb_service()
# Restart the service only if it is running
if self._stored.started:
self.unit.status = MaintenanceStatus("Reloading srsenb")
service_restart(SRS_ENB_SERVICE)
self.unit.status = self._get_current_status()
def _configure_srsenb_service(self):
self._configure_service(
command=self._get_srsenb_command(),
service_template=SRS_ENB_SERVICE_TEMPLATE,
service_path=SRS_ENB_SERVICE_PATH,
)
def _configure_srsue_service(self):
self._configure_service(
command=self._get_srsue_command(),
service_template=SRS_UE_SERVICE_TEMPLATE,
service_path=SRS_UE_SERVICE_PATH,
)
def _configure_service(
self, command: str, service_template: str, service_path: str
):
with open(service_template, "r") as template:
service_content = Template(template.read()).render(command=command)
with open(service_path, "w") as service:
service.write(service_content)
systemctl_daemon_reload()
def _get_srsenb_command(self):
srsenb_command = [SRS_ENB_BINARY]
if self._stored.mme_addr:
srsenb_command.append(f"--enb.mme_addr={self._stored.mme_addr}")
if self._stored.bind_addr:
srsenb_command.append(f"--enb.gtp_bind_addr={self._stored.bind_addr}")
srsenb_command.append(f"--enb.s1c_bind_addr={self._stored.bind_addr}")
srsenb_command.append(f'--enb.name={self.config.get("enb-name")}')
srsenb_command.append(f'--enb.mcc={self.config.get("enb-mcc")}')
srsenb_command.append(f'--enb.mnc={self.config.get("enb-mnc")}')
srsenb_command.append(f'--enb_files.rr_config={CONFIG_PATHS["rr"]}')
srsenb_command.append(f'--enb_files.sib_config={CONFIG_PATHS["sib"]}')
srsenb_command.append(f'--enb_files.drb_config={CONFIG_PATHS["drb"]}')
srsenb_command.append(CONFIG_PATHS["enb"])
srsenb_command.append(
f'--rf.device_name={self.config.get("enb-rf-device-name")}'
)
srsenb_command.append(
f'--rf.device_args={self.config.get("enb-rf-device-args")}'
)
return " ".join(srsenb_command)
def _get_srsue_command(self):
srsue_command = [SRS_UE_BINARY]
if self._stored.ue_usim_imsi:
srsue_command.append(f"--usim.imsi={self._stored.ue_usim_imsi}")
srsue_command.append(f"--usim.k={self._stored.ue_usim_k}")
srsue_command.append(f"--usim.opc={self._stored.ue_usim_opc}")
srsue_command.append(f'--usim.algo={self.config.get("ue-usim-algo")}')
srsue_command.append(f'--nas.apn={self.config.get("ue-nas-apn")}')
srsue_command.append(f'--rf.device_name={self.config.get("ue-device-name")}')
srsue_command.append(f'--rf.device_args={self.config.get("ue-device-args")}')
srsue_command.append(CONFIG_PATHS["ue"])
return " ".join(srsue_command)
# Private functions
def _reset_environment(self):
# Remove old folders (if they exist)
shutil.rmtree(SRC_PATH, ignore_errors=True)
shutil.rmtree(BUILD_PATH, ignore_errors=True)
shutil.rmtree(CONFIG_PATH, ignore_errors=True)
shutil.rmtree(SERVICE_PATH, ignore_errors=True)
# Create needed folders
os.mkdir(SRC_PATH)
os.mkdir(BUILD_PATH)
os.mkdir(CONFIG_PATH)
os.mkdir(SERVICE_PATH)
def _get_bind_address(self):
bind_addr = None
bind_address_subnet = self.model.config.get("bind-address-subnet")
if bind_address_subnet:
bind_addr = ip_from_iface(bind_address_subnet)
else:
bind_addr = ip_from_default_iface()
return bind_addr
def _get_current_status(self):
status_type = ActiveStatus
status_msg = ""
if self._stored.installed:
status_msg = "SW installed."
if self._stored.started and service_active(SRS_ENB_SERVICE):
status_msg = "srsenb started. "
if mme_addr := self._stored.mme_addr:
status_msg += f"mme: {mme_addr}. "
if self._stored.ue_attached and service_active(SRS_UE_SERVICE):
status_msg += "ue attached. "
return status_type(status_msg)
if __name__ == "__main__":
main(SrsLteCharm)
import subprocess
import apt
import netifaces
import shutil
from typing import Dict, List, NoReturn
from netaddr import IPNetwork, IPAddress
from netaddr.core import AddrFormatError
def service_active(service_name: str):
result = subprocess.run(
["systemctl", "is-active", service_name],
stdout=subprocess.PIPE,
encoding="utf-8",
)
return result.stdout == "active\n"
def all_values_set(dictionary: Dict[str, str]) -> bool:
return not any(v is None for v in dictionary.values())
def install_apt(packages: List, update: bool = False) -> NoReturn:
cache = apt.cache.Cache()
if update:
cache.update()
cache.open()
for package in packages:
pkg = cache[package]
if not pkg.is_installed:
pkg.mark_install()
cache.commit()
def git_clone(
repo: str,
output_folder: str = None,
branch: str = None,
depth: int = None,
):
command = ["git", "clone"]
if branch:
command.append(f"--branch={branch}")
if depth:
command.append(f"--depth={depth}")
command.append(repo)
if output_folder:
command.append(output_folder)
subprocess.run(command).check_returncode()
def shell(command: str) -> NoReturn:
subprocess.run(command, shell=True).check_returncode()
def copy_files(origin: Dict[str, str], destination: Dict[str, str]) -> NoReturn:
for config, origin_path in origin.items():
destination_path = destination[config]
shutil.copy(origin_path, destination_path)
def get_local_ipv4_networks():
networks = []
interfaces = netifaces.interfaces()
for interface in interfaces:
addresses = netifaces.ifaddresses(interface)
if netifaces.AF_INET in addresses:
ipv4_addr = addresses[netifaces.AF_INET][0]
network = IPNetwork(f'{ipv4_addr["addr"]}/{ipv4_addr["netmask"]}')
networks.append(network)
return networks
def is_ipv4(ip: str) -> bool:
try:
if not isinstance(ip, str) or len(ip.split(".")) != 4:
return False
IPAddress(ip)
return True
except AddrFormatError:
return False
# Service functions
def _systemctl(action: str, service_name: str) -> NoReturn:
subprocess.run(["systemctl", action, service_name]).check_returncode()
def service_start(service_name: str) -> NoReturn:
_systemctl("start", service_name)
def service_restart(service_name: str) -> NoReturn:
_systemctl("restart", service_name)
def service_stop(service_name: str) -> NoReturn:
_systemctl("stop", service_name)
def service_enable(service_name: str) -> NoReturn:
_systemctl("enable", service_name)
def systemctl_daemon_reload():
subprocess.run(["systemctl", "daemon-reload"]).check_returncode()
def ip_from_default_iface() -> str:
default_gateway = netifaces.gateways()["default"]
if netifaces.AF_INET in default_gateway:
_, iface = netifaces.gateways()["default"][netifaces.AF_INET]
default_interface = netifaces.ifaddresses(iface)
if netifaces.AF_INET in default_interface:
return netifaces.ifaddresses(iface)[netifaces.AF_INET][0].get("addr")
def ip_from_iface(subnet: str) -> str:
try:
target_network = IPNetwork(subnet)
networks = get_local_ipv4_networks()
for network in networks:
if network.ip in target_network:
return network.ip.format()
except AddrFormatError:
return
[Unit]
Description=Srs EnodeB Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=root
ExecStart={{ command }}
[Install]
WantedBy=multi-user.target
[Unit]
Description=Srs User Emulator Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
ExecStart={{ command }}
User=root
KillSignal=SIGINT
TimeoutStopSec=10
ExecStopPost=service srsenb restart
[Install]
WantedBy=multi-user.target
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.
import unittest
from unittest.mock import Mock
from ops.testing import Harness
from charm import SrsLteCharm
class TestCharm(unittest.TestCase):
def test_config_changed(self):
harness = Harness(SrsLteCharm)
self.addCleanup(harness.cleanup)
harness.begin()
self.assertEqual(list(harness.charm._stored.things), [])
harness.update_config({"thing": "foo"})
self.assertEqual(list(harness.charm._stored.things), ["foo"])
def test_action(self):
harness = Harness(SrsLteCharm)
harness.begin()
# the harness doesn't (yet!) help much with actions themselves
action_event = Mock(params={"fail": ""})
harness.charm._on_fortune_action(action_event)
self.assertTrue(action_event.set_results.called)
def test_action_fail(self):
harness = Harness(SrsLteCharm)
harness.begin()
action_event = Mock(params={"fail": "fail this"})
harness.charm._on_fortune_action(action_event)
self.assertEqual(action_event.fail.call_args, [("fail this",)])
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Metadata-Version: 2.1
Name: Jinja2
Version: 2.11.2
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Code, https://github.com/pallets/jinja
Project-URL: Issue tracker, https://github.com/pallets/jinja/issues
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
Description-Content-Type: text/x-rst
Requires-Dist: MarkupSafe (>=0.23)
Provides-Extra: i18n
Requires-Dist: Babel (>=0.8) ; extra == 'i18n'
Jinja
=====
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
It includes:
- Template inheritance and inclusion.
- Define and import macros within templates.
- HTML templates can use autoescaping to prevent XSS from untrusted
user input.
- A sandboxed environment can safely render untrusted templates.
- AsyncIO support for generating templates and calling async
functions.
- I18N support with Babel.
- Templates are compiled to optimized Python code just-in-time and
cached, or can be compiled ahead-of-time.
- Exceptions point to the correct line in templates to make debugging
easier.
- Extensible filters, tests, functions, and even syntax.
Jinja's philosophy is that while application logic belongs in Python if
possible, it shouldn't make the template designer's job difficult by
restricting functionality too much.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Jinja2
.. _pip: https://pip.pypa.io/en/stable/quickstart/
In A Nutshell
-------------
.. code-block:: jinja
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Links
-----
- Website: https://palletsprojects.com/p/jinja/
- Documentation: https://jinja.palletsprojects.com/
- Releases: https://pypi.org/project/Jinja2/
- Code: https://github.com/pallets/jinja
- Issue tracker: https://github.com/pallets/jinja/issues
- Test status: https://dev.azure.com/pallets/jinja/_build
- Official chat: https://discord.gg/t6rrQZH
Jinja2-2.11.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Jinja2-2.11.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
Jinja2-2.11.2.dist-info/METADATA,sha256=5ZHRZoIRAMHsJPnqhlJ622_dRPsYePYJ-9EH4-Ry7yI,3535
Jinja2-2.11.2.dist-info/RECORD,,
Jinja2-2.11.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Jinja2-2.11.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
Jinja2-2.11.2.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
Jinja2-2.11.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
jinja2/__init__.py,sha256=0QCM_jKKDM10yzSdHRVV4mQbCbDqf0GN0GirAqibn9Y,1549
jinja2/__pycache__/__init__.cpython-37.pyc,,
jinja2/__pycache__/_compat.cpython-37.pyc,,
jinja2/__pycache__/_identifier.cpython-37.pyc,,
jinja2/__pycache__/asyncfilters.cpython-37.pyc,,
jinja2/__pycache__/asyncsupport.cpython-37.pyc,,
jinja2/__pycache__/bccache.cpython-37.pyc,,
jinja2/__pycache__/compiler.cpython-37.pyc,,
jinja2/__pycache__/constants.cpython-37.pyc,,
jinja2/__pycache__/debug.cpython-37.pyc,,
jinja2/__pycache__/defaults.cpython-37.pyc,,
jinja2/__pycache__/environment.cpython-37.pyc,,
jinja2/__pycache__/exceptions.cpython-37.pyc,,
jinja2/__pycache__/ext.cpython-37.pyc,,
jinja2/__pycache__/filters.cpython-37.pyc,,
jinja2/__pycache__/idtracking.cpython-37.pyc,,
jinja2/__pycache__/lexer.cpython-37.pyc,,
jinja2/__pycache__/loaders.cpython-37.pyc,,
jinja2/__pycache__/meta.cpython-37.pyc,,
jinja2/__pycache__/nativetypes.cpython-37.pyc,,
jinja2/__pycache__/nodes.cpython-37.pyc,,
jinja2/__pycache__/optimizer.cpython-37.pyc,,
jinja2/__pycache__/parser.cpython-37.pyc,,
jinja2/__pycache__/runtime.cpython-37.pyc,,
jinja2/__pycache__/sandbox.cpython-37.pyc,,
jinja2/__pycache__/tests.cpython-37.pyc,,
jinja2/__pycache__/utils.cpython-37.pyc,,
jinja2/__pycache__/visitor.cpython-37.pyc,,
jinja2/_compat.py,sha256=B6Se8HjnXVpzz9-vfHejn-DV2NjaVK-Iewupc5kKlu8,3191
jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775
jinja2/asyncfilters.py,sha256=XJtYXTxFvcJ5xwk6SaDL4S0oNnT0wPYvXBCSzc482fI,4250
jinja2/asyncsupport.py,sha256=ZBFsDLuq3Gtji3Ia87lcyuDbqaHZJRdtShZcqwpFnSQ,7209
jinja2/bccache.py,sha256=3Pmp4jo65M9FQuIxdxoDBbEDFwe4acDMQf77nEJfrHA,12139
jinja2/compiler.py,sha256=Ta9W1Lit542wItAHXlDcg0sEOsFDMirCdlFPHAurg4o,66284
jinja2/constants.py,sha256=RR1sTzNzUmKco6aZicw4JpQpJGCuPuqm1h1YmCNUEFY,1458
jinja2/debug.py,sha256=neR7GIGGjZH3_ILJGVUYy3eLQCCaWJMXOb7o0kGInWc,8529
jinja2/defaults.py,sha256=85B6YUUCyWPSdrSeVhcqFVuu_bHUAQXeey--FIwSeVQ,1126
jinja2/environment.py,sha256=XDSLKc4SqNLMOwTSq3TbWEyA5WyXfuLuVD0wAVjEFwM,50629
jinja2/exceptions.py,sha256=VjNLawcmf2ODffqVMCQK1cRmvFaUfQWF4u8ouP3QPcE,5425
jinja2/ext.py,sha256=AtwL5O5enT_L3HR9-oBvhGyUTdGoyaqG_ICtnR_EVd4,26441
jinja2/filters.py,sha256=_RpPgAlgIj7ExvyDzcHAC3B36cocfWK-1TEketbNeM0,41415
jinja2/idtracking.py,sha256=J3O4VHsrbf3wzwiBc7Cro26kHb6_5kbULeIOzocchIU,9211
jinja2/lexer.py,sha256=nUFLRKhhKmmEWkLI65nQePgcQs7qsRdjVYZETMt_v0g,30331
jinja2/loaders.py,sha256=C-fST_dmFjgWkp0ZuCkrgICAoOsoSIF28wfAFink0oU,17666
jinja2/meta.py,sha256=QjyYhfNRD3QCXjBJpiPl9KgkEkGXJbAkCUq4-Ur10EQ,4131
jinja2/nativetypes.py,sha256=Ul__gtVw4xH-0qvUvnCNHedQeNDwmEuyLJztzzSPeRg,2753
jinja2/nodes.py,sha256=Mk1oJPVgIjnQw9WOqILvcu3rLepcFZ0ahxQm2mbwDwc,31095
jinja2/optimizer.py,sha256=gQLlMYzvQhluhzmAIFA1tXS0cwgWYOjprN-gTRcHVsc,1457
jinja2/parser.py,sha256=fcfdqePNTNyvosIvczbytVA332qpsURvYnCGcjDHSkA,35660
jinja2/runtime.py,sha256=0y-BRyIEZ9ltByL2Id6GpHe1oDRQAwNeQvI0SKobNMw,30618
jinja2/sandbox.py,sha256=knayyUvXsZ-F0mk15mO2-ehK9gsw04UhB8td-iUOtLc,17127
jinja2/tests.py,sha256=iO_Y-9Vo60zrVe1lMpSl5sKHqAxe2leZHC08OoZ8K24,4799
jinja2/utils.py,sha256=OoVMlQe9S2-lWT6jJbTu9tDuDvGNyWUhHDcE51i5_Do,22522
jinja2/visitor.py,sha256=DUHupl0a4PGp7nxRtZFttUzAi1ccxzqc2hzetPYUz8U,3240
Wheel-Version: 1.0
Generator: bdist_wheel (0.34.2)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Metadata-Version: 2.1
Name: MarkupSafe
Version: 1.1.1
Summary: Safely add untrusted strings to HTML/XML markup.
Home-page: https://palletsprojects.com/p/markupsafe/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: The Pallets Team
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
Project-URL: Code, https://github.com/pallets/markupsafe
Project-URL: Issue tracker, https://github.com/pallets/markupsafe/issues
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
MarkupSafe
==========
MarkupSafe implements a text object that escapes characters so it is
safe to use in HTML and XML. Characters that have special meanings are
replaced so that they display as the actual characters. This mitigates
injection attacks, meaning untrusted user input can safely be displayed
on a page.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U MarkupSafe
.. _pip: https://pip.pypa.io/en/stable/quickstart/
Examples
--------
.. code-block:: pycon
>>> from markupsafe import Markup, escape
>>> # escape replaces special characters and wraps in Markup
>>> escape('<script>alert(document.cookie);</script>')
Markup(u'&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
>>> # wrap in Markup to mark text "safe" and prevent escaping
>>> Markup('<strong>Hello</strong>')
Markup('<strong>hello</strong>')
>>> escape(Markup('<strong>Hello</strong>'))
Markup('<strong>hello</strong>')
>>> # Markup is a text subclass (str on Python 3, unicode on Python 2)
>>> # methods and operators escape their arguments
>>> template = Markup("Hello <em>%s</em>")
>>> template % '"World"'
Markup('Hello <em>&#34;World&#34;</em>')
Donate
------
The Pallets organization develops and supports MarkupSafe and other
libraries that use it. In order to grow the community of contributors
and users, and allow the maintainers to devote more time to the
projects, `please donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
* Website: https://palletsprojects.com/p/markupsafe/
* Documentation: https://markupsafe.palletsprojects.com/
* License: `BSD-3-Clause <https://github.com/pallets/markupsafe/blob/master/LICENSE.rst>`_
* Releases: https://pypi.org/project/MarkupSafe/
* Code: https://github.com/pallets/markupsafe
* Issue tracker: https://github.com/pallets/markupsafe/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/markupsafe
* Windows: https://ci.appveyor.com/project/pallets/markupsafe
* Test coverage: https://codecov.io/gh/pallets/markupsafe
MarkupSafe-1.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
MarkupSafe-1.1.1.dist-info/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
MarkupSafe-1.1.1.dist-info/METADATA,sha256=nJHwJ4_4ka-V39QH883jPrslj6inNdyyNASBXbYgHXQ,3570
MarkupSafe-1.1.1.dist-info/RECORD,,
MarkupSafe-1.1.1.dist-info/WHEEL,sha256=AhV6RMqZ2IDfreRJKo44QWYxYeP-0Jr0bezzBLQ1eog,109
MarkupSafe-1.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
markupsafe/__init__.py,sha256=oTblO5f9KFM-pvnq9bB0HgElnqkJyqHnFN1Nx2NIvnY,10126
markupsafe/__pycache__/__init__.cpython-37.pyc,,
markupsafe/__pycache__/_compat.cpython-37.pyc,,
markupsafe/__pycache__/_constants.cpython-37.pyc,,
markupsafe/__pycache__/_native.cpython-37.pyc,,
markupsafe/_compat.py,sha256=uEW1ybxEjfxIiuTbRRaJpHsPFf4yQUMMKaPgYEC5XbU,558
markupsafe/_constants.py,sha256=zo2ajfScG-l1Sb_52EP3MlDCqO7Y1BVHUXXKRsVDRNk,4690
markupsafe/_native.py,sha256=d-8S_zzYt2y512xYcuSxq0NeG2DUUvG80wVdTn-4KI8,1873
markupsafe/_speedups.c,sha256=k0fzEIK3CP6MmMqeY0ob43TP90mVN0DTyn7BAl3RqSg,9884
markupsafe/_speedups.cpython-37m-x86_64-linux-gnu.so,sha256=pz-ucGdAq6kJtq9lEY1kY2Ed6LQjbRrIicdu_i4HFqU,38875
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.1)
Root-Is-Purelib: false
Tag: cp37-cp37m-manylinux1_x86_64
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment