<commit_before>#!/usr/bin/python

import sys
import os
import os.path
import invoke
import stdio

CBR_WRAPPER = "scripts/util/cbr_wrapper.sh"

def FindRoot(start):
    for offset in ['.', '..', '../..', '../../..']:
        offset_dir = start + '/' + offset
        test_file = offset_dir + '/install-deps.sh'
        if (os.path.isfile(test_file)):
            return offset_dir
    return None

def RunCBR(args, io=None, **kwargs):
    # Get the *scripts* directory, then just run the wrapper
    root_dir = FindRoot( os.getcwd() )
    if root_dir == None:
        print "RunCBR: Couldn't find root directory from current directory."
        return

    cmd = [root_dir + '/' + CBR_WRAPPER]
    cmd.extend(args)

    # Setup our IO, using default IO but overriding with parameters
    if io == None:
        io = stdio.StdIO()
    if ('stdin' in kwargs):
        io.stdin = kwargs['stdin']
        del kwargs['stdin']
    if ('stdout' in kwargs):
        io.stdout = kwargs['stdout']
        del kwargs['stdout']
    if ('stderr' in kwargs):
        io.stderr = kwargs['stderr']
        del kwargs['stderr']

    invoke.invoke(cmd, io=io, **kwargs)

if __name__ == "__main__":
    RunCBR(sys.argv)
<commit_msg>Fix bug in cbr_wrapper.  It should create a new StdIO object using the old object as a template, then replace elements in that new one so it doesn't permanently affect the one passed by the user.<commit_after>#!/usr/bin/python

import sys
import os
import os.path
import invoke
import stdio

CBR_WRAPPER = "scripts/util/cbr_wrapper.sh"

def FindRoot(start):
    for offset in ['.', '..', '../..', '../../..']:
        offset_dir = start + '/' + offset
        test_file = offset_dir + '/install-deps.sh'
        if (os.path.isfile(test_file)):
            return offset_dir
    return None

def RunCBR(args, io=None, **kwargs):
    # Get the *scripts* directory, then just run the wrapper
    root_dir = FindRoot( os.getcwd() )
    if root_dir == None:
        print "RunCBR: Couldn't find root directory from current directory."
        return

    cmd = [root_dir + '/' + CBR_WRAPPER]
    cmd.extend(args)

    # Setup our IO, using default IO but overriding with parameters
    if io == None:
        io = stdio.StdIO()
    our_io = stdio.StdIO(io.stdin, io.stdout, io.stderr)
    if ('stdin' in kwargs):
        our_io.stdin = kwargs['stdin']
        del kwargs['stdin']
    if ('stdout' in kwargs):
        our_io.stdout = kwargs['stdout']
        del kwargs['stdout']
    if ('stderr' in kwargs):
        our_io.stderr = kwargs['stderr']
        del kwargs['stderr']

    invoke.invoke(cmd, io=our_io, **kwargs)

if __name__ == "__main__":
    RunCBR(sys.argv)
<|endoftext|>


<commit_before>###############################################################################
## Copyright (c) 2013-2014, Intel Corporation                                ##
## All rights reserved.                                                      ##
##                                                                           ##
## 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.              ##
###############################################################################
## Christopher Dahnken (Intel Corp.), Hans Pabst (Intel Corp.),
## Alfio Lazzaro (CRAY Inc.), and Gilles Fourestey (CSCS)
###############################################################################
import math
import sys

def create_symmetric_interface(dimsM,dimsN,dimsK,RowMajor):
    print "#include <xsmm_knc.h>"
    print "#include <mkl.h>"
    print "#include <stdio.h>"
    print "void xsmm_dnn(int M, int N, int K, const double* a, const double* b, double* c) {"
    print "  int v=((M-1)<<10)+((N-1)<<5)+(K-1);"
    print "  switch(v) {"
    for m in dimsM:
        for n in dimsN:
           for k in dimsK:
                print "    case "+str(((m-1)<<10)+((n-1)<<5)+(k-1))+":"
                print "         dc_smm_dnn_"+str(m)+"_"+str(n)+"_"+str(k)+"(a,b,c);"
                print "         break;"
    print "    default:"
    if RowMajor==1:
        print "         cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, M, N, K, 1.0, a, K, b, N, 1.0, c, N);"
    else:
        print "         cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, M, N, K, 1.0, a, K, b, N, 1.0, c, N);"
    print "  }"
    print "}"

def load_dims(dims):
    dims=map(int, dims) ; dims.sort()
    return list(set(dims))

dimsM = load_dims(sys.argv[4:4+int(sys.argv[1])])
dimsN = load_dims(sys.argv[4+int(sys.argv[1]):4+int(sys.argv[1])+int(sys.argv[2])])
dimsK = load_dims(sys.argv[4+int(sys.argv[1])+int(sys.argv[2]):])

create_symmetric_interface(dimsM,dimsN,dimsK,int(sys.argv[3]))
<commit_msg>Improved the function dispatch to only take the minimum number of branches (via bsearch). There is one additional branch to check that the indexes are within bounds; there is also an early-out for consecutive binary searches due to if's evaluation of the condition clause. Note that the entire dispatch would be much simpler if we would not support non-contiguous index sets (sparse indexes). The Python script already sorted the indexes; a prerequisite for the binary search. The generated source code is now also beautified and more readable.<commit_after>###############################################################################
## Copyright (c) 2013-2014, Intel Corporation                                ##
## All rights reserved.                                                      ##
##                                                                           ##
## 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.              ##
###############################################################################
## Christopher Dahnken (Intel Corp.), Hans Pabst (Intel Corp.),
## Alfio Lazzaro (CRAY Inc.), and Gilles Fourestey (CSCS)
###############################################################################
import math
import sys

def create_symmetric_interface(dimsM,dimsN,dimsK,RowMajor):
    print "#include \"xsmm_knc.h\""
    print "#include <stdlib.h>"
    print "#include <mkl.h>"
    print
    print
    print "int compareints(const void* a, const void* b)"
    print "{"
    print "  return *((const int*)a) - *((const int*)b);"
    print "}"
    print
    print
    print "void xsmm_dnn(int M, int N, int K, const double* a, const double* b, double* c)"
    print "{"
    print "  static const int index_m[] = { "+str(dimsM).strip("[]")+" }, nm = sizeof(index_m) / sizeof(M);"
    print "  static const int index_n[] = { "+str(dimsN).strip("[]")+" }, nn = sizeof(index_n) / sizeof(N);"
    print "  static const int index_k[] = { "+str(dimsK).strip("[]")+" }, nk = sizeof(index_k) / sizeof(K);"
    print "  static void (*ftable[])(const double*, const double*, double*) = {"
    for m in dimsM:
        for n in dimsN:
           sys.stdout.write("    ")
           for k in dimsK:
                sys.stdout.write("dc_smm_dnn_"+str(m)+"_"+str(n)+"_"+str(k)+", ")
           print "// m = %d" % m
    print "  };"
    print
    print "  int m, n, k;"
    print "  if ((m = (int*)bsearch(&M, index_m, nm, sizeof(M), compareints) - index_m) < nm"
    print "   && (n = (int*)bsearch(&N, index_n, nn, sizeof(N), compareints) - index_n) < nn"
    print "   && (k = (int*)bsearch(&K, index_k, nk, sizeof(K), compareints) - index_k) < nk)"
    print "  {"
    print "    const int i = nk * (m * nn + n) + k;"
    print "    void (*f)(const double*, const double*, double*) = ftable[i];"
    print "    (*f)(a, b, c);"
    print "  }"
    print "  else {"
    if 0 != RowMajor:
        print "    cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, M, N, K, 1.0, a, K, b, N, 1.0, c, N);"
    else:
        print "    cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, M, N, K, 1.0, a, K, b, N, 1.0, c, N);"
    print "  }"
    print "}"

def load_dims(dims):
    dims=map(int, dims) ; dims.sort()
    return list(set(dims))

dimsM = load_dims(sys.argv[4:4+int(sys.argv[1])])
dimsN = load_dims(sys.argv[4+int(sys.argv[1]):4+int(sys.argv[1])+int(sys.argv[2])])
dimsK = load_dims(sys.argv[4+int(sys.argv[1])+int(sys.argv[2]):])

create_symmetric_interface(dimsM,dimsN,dimsK,int(sys.argv[3]))
<|endoftext|>


<commit_before>#!/usr/bin/env python3

"""
Created on 17 Apr 2017

@author: Bruno Beloff (bruno.beloff@southcoastscience.com)

Warning: osio_mqtt_client should be started before control_receiver.

command line example:
cat ~/SCS/pipes/control_subscription_pipe | ./osio_topic_subscriber.py -cX | ./control_receiver.py -r -v | \
./osio_topic_publisher.py -cX > ~/SCS/pipes/control_publication_pipe
"""

import json
import sys

from collections import OrderedDict

from scs_core.control.control_datum import ControlDatum
from scs_core.control.control_receipt import ControlReceipt

from scs_core.data.json import JSONify
from scs_core.data.localized_datetime import LocalizedDatetime

from scs_core.sys.exception_report import ExceptionReport
from scs_core.sys.system_id import SystemID

from scs_dev.cmd.cmd_control_receiver import CmdControlReceiver

from scs_host.sys.host import Host

# --------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':

    # ----------------------------------------------------------------------------------------------------------------
    # cmd...

    cmd = CmdControlReceiver()

    if not cmd.is_valid():
        cmd.print_help(sys.stderr)
        exit()

    if cmd.verbose:
        print(cmd, file=sys.stderr)

    # ------------------------------------------------------------------------------------------------------------
    # resources...

    if cmd.tag:
        tag = cmd.tag
        subscriber_sn = cmd.serial_number

    else:
        # SystemID...
        system_id = SystemID.load_from_host(Host)

        if system_id is None:
            print("SystemID not available.", file=sys.stderr)
            exit()

        if cmd.verbose:
            print(system_id, file=sys.stderr)
            sys.stderr.flush()

        tag = system_id.message_tag()
        subscriber_sn = Host.serial_number()

    try:
        # ------------------------------------------------------------------------------------------------------------
        # run...

        for line in sys.stdin:
            jdict = json.loads(line, object_pairs_hook=OrderedDict)

            try:
                datum = ControlDatum.construct_from_jdict(jdict)
            except TypeError:
                continue

            if datum.attn != tag:
                continue

            if cmd.verbose:
                print(datum, file=sys.stderr)
                sys.stderr.flush()

            if not datum.is_valid(subscriber_sn):
                print("control_receiver: invalid digest: %s" % datum, file=sys.stderr)
                continue

            if cmd.echo:
                print(JSONify.dumps(datum))
                sys.stdout.flush()

            # TODO: check command tokens here

            if cmd.receipt:
                now = LocalizedDatetime.now()
                receipt = ControlReceipt.construct_from_datum(datum, now, 'OK', subscriber_sn)

                print(JSONify.dumps(receipt))
                sys.stdout.flush()

                if cmd.verbose:
                    print(receipt, file=sys.stderr)
                    sys.stderr.flush()

            # TODO: perform command here

    # ----------------------------------------------------------------------------------------------------------------
    # end...

    except KeyboardInterrupt as ex:
        if cmd.verbose:
            print("control_receiver: KeyboardInterrupt", file=sys.stderr)

    except Exception as ex:
        print(JSONify.dumps(ExceptionReport.construct(ex)), file=sys.stderr)
<commit_msg>Added command handling to ControlReceipt.<commit_after>#!/usr/bin/env python3

"""
Created on 17 Apr 2017

@author: Bruno Beloff (bruno.beloff@southcoastscience.com)

Warning: osio_mqtt_client should be started before control_receiver.

command line example:
cat ~/SCS/pipes/control_subscription_pipe | ./osio_topic_subscriber.py -cX | ./control_receiver.py -r -v | \
./osio_topic_publisher.py -cX > ~/SCS/pipes/control_publication_pipe
"""

import json
import sys

from collections import OrderedDict

from scs_core.control.command import Command
from scs_core.control.control_datum import ControlDatum
from scs_core.control.control_receipt import ControlReceipt

from scs_core.data.json import JSONify
from scs_core.data.localized_datetime import LocalizedDatetime

from scs_core.sys.exception_report import ExceptionReport
from scs_core.sys.system_id import SystemID

from scs_dev.cmd.cmd_control_receiver import CmdControlReceiver

from scs_host.sys.host import Host

# --------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':

    # ----------------------------------------------------------------------------------------------------------------
    # cmd...

    cmd = CmdControlReceiver()

    if not cmd.is_valid():
        cmd.print_help(sys.stderr)
        exit()

    if cmd.verbose:
        print(cmd, file=sys.stderr)

    # ------------------------------------------------------------------------------------------------------------
    # resources...

    if cmd.tag:
        tag = cmd.tag
        subscriber_sn = cmd.serial_number

    else:
        # SystemID...
        system_id = SystemID.load_from_host(Host)

        if system_id is None:
            print("SystemID not available.", file=sys.stderr)
            exit()

        if cmd.verbose:
            print(system_id, file=sys.stderr)
            sys.stderr.flush()

        tag = system_id.message_tag()
        subscriber_sn = Host.serial_number()

    try:
        # ------------------------------------------------------------------------------------------------------------
        # run...

        for line in sys.stdin:
            jdict = json.loads(line, object_pairs_hook=OrderedDict)

            try:
                datum = ControlDatum.construct_from_jdict(jdict)
            except TypeError:
                continue

            if datum.attn != tag:
                continue

            if cmd.verbose:
                print(datum, file=sys.stderr)
                sys.stderr.flush()

            if not datum.is_valid(subscriber_sn):
                print("control_receiver: invalid digest: %s" % datum, file=sys.stderr)
                continue

            if cmd.echo:
                print(JSONify.dumps(datum))
                sys.stdout.flush()

            command = Command.construct_from_tokens(datum.cmd_tokens)
            command.execute(Host)

            if cmd.receipt:
                now = LocalizedDatetime.now()
                receipt = ControlReceipt.construct_from_datum(datum, now, command, subscriber_sn)

                print(JSONify.dumps(receipt))
                sys.stdout.flush()

                if cmd.verbose:
                    print(receipt, file=sys.stderr)
                    sys.stderr.flush()

            # TODO: perform reboot commands here

    # ----------------------------------------------------------------------------------------------------------------
    # end...

    except KeyboardInterrupt as ex:
        if cmd.verbose:
            print("control_receiver: KeyboardInterrupt", file=sys.stderr)

    except Exception as ex:
        print(JSONify.dumps(ExceptionReport.construct(ex)), file=sys.stderr)
<|endoftext|>


<commit_before>import re
from collections import namedtuple
from enum import Enum, auto
from string import Template

import requests
from pkg_resources import parse_version
from repodataParser.RepoParser import Parser

from sdcm.utils.common import ParallelObject

# Examples of ScyllaDB version strings:
#   - 666.development-0.20200205.2816404f575
#   - 3.3.rc1-0.20200209.0d0c1d43188
#   - 2019.1.4-0.20191217.b59e92dbd

# gemini version 1.0.1, commit ef7c6f422c78ef6b84a6f3bccf52ea9ec846bba0, date 2019-05-16T09:56:16Z
GEMINI_VERSION_RE = re.compile(r'\s(?P<gemini_version>([\d]+\.[\d]+\.[\d]+)?),')
REPO_VERSIONS_REGEX = re.compile(r'Version: (.*?)\n', re.DOTALL)

SCYLLA_VERSION_RE = re.compile(r"\d+(\.\d+)?\.[\d\w]+([.~][\d\w]+)?")
PRIMARY_XML_GZ_REGEX = re.compile(r'="(.*?primary.xml.gz)"')

REPOMD_XML_PATH = "repodata/repomd.xml"
SCYLLA_URL_REGEX = re.compile(".*(?P<url>http.*scylladb.com.*).*")

BUILD_ID_RE = re.compile(r"Build ID: (?P<build_id>\w+)")

SCYLLA_URL_RESPONSE_TIMEOUT = 30
SUPPORTED_XML_EXTENSIONS = ("xml", "xml.gz")
SUPPORTED_FILE_EXTENSIONS = ("list", "repo", "Packages", "gz") + SUPPORTED_XML_EXTENSIONS
VERSION_NOT_FOUND_ERROR = "The URL not supported, only Debian and Yum are supported"

class ScyllaFileType(Enum):
    DEBIAN = auto()
    YUM = auto()

FILE_REGEX_DICT = {
    ScyllaFileType.DEBIAN: [
        (re.compile(r"deb\s+\[arch=(?P<arch>.*?)\]\s(?P<url>http.*?)\s(?P<version_code_name>.*?)\s(?P<component>.*?)$"),
         "{url}/dists/{version_code_name}/{component}/binary-{arch}/Packages"),
    ],
    ScyllaFileType.YUM:
        [
            (re.compile(r"baseurl=(?P<url>http.*scylladb.com.*)"), "{url}" + REPOMD_XML_PATH),
            (re.compile(r"deb\s+(?P<url>http.*?)\s(?P<version_code_name>.*?)\s(?P<component>.*?)$"),
             "{url}/dists/{version_code_name}/{component}/binary-amd64/Packages.gz"),
    ],
}

# The variable "type" indices the type of URL (Debian or Yum)
# The variable "urls" contains all urls using the Scylla pattern
RepositoryDetails = namedtuple("RepositoryDetails", ["type", "urls"])

def get_url_content(url, return_url_data=True):
    response = requests.get(url=url)
    if response.status_code != 200:
        raise ValueError(f"The following repository URL '{url}' is incorrect")
    response_data = response.text
    if not response_data:
        raise ValueError(f"The repository URL '{url}' not contains any content")
    if return_url_data:
        return response_data.split('\n')
    # To overcome on Pylint's "inconsistent-return-statements", a value must be returned    return []
    return []

def get_scylla_urls_from_repository(repo_details):
    urls = set()
    for url in repo_details.urls:
        for (url_regex, url_format) in FILE_REGEX_DICT[repo_details.type]:
            match = url_regex.match(url)
            if not match:
                # Continue until find the correct Regex
                continue

            full_url = url_format.format(**match.groupdict())
            # for scylla-manager we never used a noarch key
            basearch_list = ["x86_64"] if 'scylla-manager' in full_url else ["x86_64", "noarch"]
            for basearch in basearch_list:
                urls.add(Template(full_url).substitute(basearch=basearch, releasever='7'))
            # We found the correct regex and we can continue to next URL
            break

    urls = list(urls)
    ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=lambda _url: get_url_content(
        url=_url, return_url_data=False))
    return urls

def get_branch_version_from_debian_repository(urls):
    def get_version(url):
        data = '\n'.join(get_url_content(url=url))
        # Get only the major version (i.e. "2019.1.1-0.20190709.9f724fedb-1~stretch", get only "2019.1.1")
        major_versions = [version.split('-', maxsplit=1)[0] for version in REPO_VERSIONS_REGEX.findall(data)]
        if not major_versions:
            return ""
        return max(set(major_versions), key=major_versions.count)

    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_version)
    result = [thread.result for thread in threads]
    return max(result, key=result.count)

def get_branch_version_from_centos_repository(urls):
    def get_version(url):
        data = '\n'.join(get_url_content(url=url))
        primary_path = PRIMARY_XML_GZ_REGEX.search(data).groups()[0]
        xml_url = url.replace(REPOMD_XML_PATH, primary_path)

        parser = Parser(url=xml_url)
        major_versions = [package['version'][1]['ver'] for package in parser.getList()]
        return max(set(major_versions), key=major_versions.count)

    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_version)
    result = [thread.result for thread in threads]
    return max(result, key=result.count)

def get_repository_details(url):
    urls = list({line for line in get_url_content(url=url) if SCYLLA_URL_REGEX.match(line)})

    for file_type, regex_list in FILE_REGEX_DICT.items():
        for _url in urls:
            for (url_regex, _) in regex_list:
                match = url_regex.match(_url)
                if match:
                    return RepositoryDetails(type=file_type, urls=urls)
    raise ValueError(VERSION_NOT_FOUND_ERROR)

def get_branch_version(url):
    repo_details = get_repository_details(url=url)
    urls = get_scylla_urls_from_repository(repo_details=repo_details)

    if repo_details.type == ScyllaFileType.DEBIAN:
        return get_branch_version_from_debian_repository(urls=urls)
    elif repo_details.type == ScyllaFileType.YUM:
        return get_branch_version_from_centos_repository(urls=urls)
    # To overcome on Pylint's "inconsistent-return-statements", a value must be returned
    return []

def get_branch_version_for_multiple_repositories(urls):
    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_branch_version)
    return [thread.result for thread in threads]

def is_enterprise(version):
    """
    :param version: version string
    :return: True if this version string passed is a scylla enterprise version
    """
    return parse_version(version) > parse_version('2000')

def get_gemini_version(output: str):
    # take only version number - 1.0.1
    result = GEMINI_VERSION_RE.search(output)

    if result:
        return result.groupdict().get("gemini_version", None)
    return None
<commit_msg>fix(version_utils.py): Enabled get Scylla version from private repo<commit_after>import re
from collections import namedtuple
from enum import Enum, auto
from string import Template

import requests
from pkg_resources import parse_version
from repodataParser.RepoParser import Parser

from sdcm.utils.common import ParallelObject

# Examples of ScyllaDB version strings:
#   - 666.development-0.20200205.2816404f575
#   - 3.3.rc1-0.20200209.0d0c1d43188
#   - 2019.1.4-0.20191217.b59e92dbd

# gemini version 1.0.1, commit ef7c6f422c78ef6b84a6f3bccf52ea9ec846bba0, date 2019-05-16T09:56:16Z
GEMINI_VERSION_RE = re.compile(r'\s(?P<gemini_version>([\d]+\.[\d]+\.[\d]+)?),')
REPO_VERSIONS_REGEX = re.compile(r'Version: (.*?)\n', re.DOTALL)

SCYLLA_VERSION_RE = re.compile(r"\d+(\.\d+)?\.[\d\w]+([.~][\d\w]+)?")
PRIMARY_XML_GZ_REGEX = re.compile(r'="(.*?primary.xml.gz)"')

REPOMD_XML_PATH = "repodata/repomd.xml"

BUILD_ID_RE = re.compile(r"Build ID: (?P<build_id>\w+)")

SCYLLA_URL_RESPONSE_TIMEOUT = 30
SUPPORTED_XML_EXTENSIONS = ("xml", "xml.gz")
SUPPORTED_FILE_EXTENSIONS = ("list", "repo", "Packages", "gz") + SUPPORTED_XML_EXTENSIONS
VERSION_NOT_FOUND_ERROR = "The URL not supported, only Debian and Yum are supported"

class ScyllaFileType(Enum):
    DEBIAN = auto()
    YUM = auto()

FILE_REGEX_DICT = {
    ScyllaFileType.DEBIAN: [
        (re.compile(r"deb\s+\[arch=(?P<arch>.*?)\]\s(?P<url>http.*?)\s(?P<version_code_name>.*?)\s(?P<component>.*?)$"),
         "{url}/dists/{version_code_name}/{component}/binary-{arch}/Packages"),
    ],
    ScyllaFileType.YUM:
        [
            (re.compile(r"baseurl=(?P<url>http.*scylladb.com.*)"), "{url}" + REPOMD_XML_PATH),
            (re.compile(r"deb\s+(?P<url>http.*?)\s(?P<version_code_name>.*?)\s(?P<component>.*?)$"),
             "{url}/dists/{version_code_name}/{component}/binary-amd64/Packages.gz"),
    ],
}

# The variable "type" indices the type of URL (Debian or Yum)
# The variable "urls" contains all urls using the Scylla pattern
RepositoryDetails = namedtuple("RepositoryDetails", ["type", "urls"])

def get_url_content(url, return_url_data=True):
    response = requests.get(url=url)
    if response.status_code != 200:
        raise ValueError(f"The following repository URL '{url}' is incorrect")
    response_data = response.text
    if not response_data:
        raise ValueError(f"The repository URL '{url}' not contains any content")
    if return_url_data:
        return response_data.split('\n')
    # To overcome on Pylint's "inconsistent-return-statements", a value must be returned    return []
    return []

def get_scylla_urls_from_repository(repo_details):
    urls = set()
    for url in repo_details.urls:
        for (url_regex, url_format) in FILE_REGEX_DICT[repo_details.type]:
            match = url_regex.match(url)
            if not match:
                # Continue until find the correct Regex
                continue

            full_url = url_format.format(**match.groupdict())
            # for scylla-manager we never used a noarch key
            basearch_list = ["x86_64"] if 'scylla-manager' in full_url else ["x86_64", "noarch"]
            for basearch in basearch_list:
                urls.add(Template(full_url).substitute(basearch=basearch, releasever='7'))
            # We found the correct regex and we can continue to next URL
            break

    urls = list(urls)
    ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=lambda _url: get_url_content(
        url=_url, return_url_data=False))
    return urls

def get_branch_version_from_debian_repository(urls):
    def get_version(url):
        data = '\n'.join(get_url_content(url=url))
        # Get only the major version (i.e. "2019.1.1-0.20190709.9f724fedb-1~stretch", get only "2019.1.1")
        major_versions = [version.split('-', maxsplit=1)[0] for version in REPO_VERSIONS_REGEX.findall(data)]
        if not major_versions:
            return ""
        return max(set(major_versions), key=major_versions.count)

    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_version)
    result = [thread.result for thread in threads]
    return max(result, key=result.count)

def get_branch_version_from_centos_repository(urls):
    def get_version(url):
        data = '\n'.join(get_url_content(url=url))
        primary_path = PRIMARY_XML_GZ_REGEX.search(data).groups()[0]
        xml_url = url.replace(REPOMD_XML_PATH, primary_path)

        parser = Parser(url=xml_url)
        major_versions = [package['version'][1]['ver'] for package in parser.getList()]
        return max(set(major_versions), key=major_versions.count)

    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_version)
    result = [thread.result for thread in threads]
    return max(result, key=result.count)

def get_repository_details(url):
    urls = list({line for line in get_url_content(url=url)})

    for file_type, regex_list in FILE_REGEX_DICT.items():
        for _url in urls:
            for (url_regex, _) in regex_list:
                match = url_regex.match(_url)
                if match:
                    return RepositoryDetails(type=file_type, urls=urls)
    raise ValueError(VERSION_NOT_FOUND_ERROR)

def get_branch_version(url):
    repo_details = get_repository_details(url=url)
    urls = get_scylla_urls_from_repository(repo_details=repo_details)

    if repo_details.type == ScyllaFileType.DEBIAN:
        return get_branch_version_from_debian_repository(urls=urls)
    elif repo_details.type == ScyllaFileType.YUM:
        return get_branch_version_from_centos_repository(urls=urls)
    # To overcome on Pylint's "inconsistent-return-statements", a value must be returned
    return []

def get_branch_version_for_multiple_repositories(urls):
    threads = ParallelObject(objects=urls, timeout=SCYLLA_URL_RESPONSE_TIMEOUT).run(func=get_branch_version)
    return [thread.result for thread in threads]

def is_enterprise(version):
    """
    :param version: version string
    :return: True if this version string passed is a scylla enterprise version
    """
    return parse_version(version) > parse_version('2000')

def get_gemini_version(output: str):
    # take only version number - 1.0.1
    result = GEMINI_VERSION_RE.search(output)

    if result:
        return result.groupdict().get("gemini_version", None)
    return None
<|endoftext|>


<commit_before>from json import loads

categories = ['music']

guest_client_id = 'b45b1aa10f1ac2941910a7f0d10f8e28'
search_url = 'https://api.soundcloud.com/search?q=%s&facet=model&limit=10&offset=0&linked_partitioning=1&client_id='+guest_client_id

def request(query, params):
    global search_url
    params['url'] = search_url % query
    return params

def response(resp):
    global base_url
    results = []
    search_res = loads(resp.text)
    for result in search_res.get('collection', []):
        if result['kind'] == 'track':
            title = result['title']
            content = result['description']
            results.append({'url': result['permalink_url'], 'title': title, 'content': content})
    return results
<commit_msg>[enh] soundcloud playlists<commit_after>from json import loads

categories = ['music']

guest_client_id = 'b45b1aa10f1ac2941910a7f0d10f8e28'
search_url = 'https://api.soundcloud.com/search?q=%s&facet=model&limit=10&offset=0&linked_partitioning=1&client_id='+guest_client_id

def request(query, params):
    global search_url
    params['url'] = search_url % query
    return params

def response(resp):
    global base_url
    results = []
    search_res = loads(resp.text)
    for result in search_res.get('collection', []):
        if result['kind'] in ('track', 'playlist'):
            title = result['title']
            content = result['description']
            results.append({'url': result['permalink_url'], 'title': title, 'content': content})
    return results
<|endoftext|>


<commit_before># seleniumbase package
__version__ = "2.4.14"
<commit_msg>Version 2.4.15<commit_after># seleniumbase package
__version__ = "2.4.15"
<|endoftext|>


<commit_before># seleniumbase package
__version__ = "3.1.5"
<commit_msg>Version 3.2.0<commit_after># seleniumbase package
__version__ = "3.2.0"
<|endoftext|>


<commit_before>#!/usr/bin/env python3
#
# Working with threading and pySerial
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C) 2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier:    BSD-3-Clause
"""\
Support threading with serial ports.
"""
import serial
import threading

class Protocol(object):
    """\
    Protocol as used by the ReaderThread. This base class provides empty
    implementations of all methods.
    """

    def connection_made(self, transport):
        """Called when reader thread is started"""

    def data_received(self, data):
        """Called with snippets received from the serial port"""

    def connection_lost(self, exc):
        """\
        Called when the serial port is closed or the reader loop terminated
        otherwise.
        """
        if isinstance(exc, Exception):
            raise exc

class Packetizer(Protocol):
    """
    Read binary packets from serial port. Packets are expected to be terminated
    with a TERMINATOR byte (null byte by default).

    The class also keeps track of the transport.
    """

    TERMINATOR = b'\0'

    def __init__(self):
        self.buffer = bytearray()
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        super(Packetizer, self).connection_lost(exc)

    def data_received(self, data):
        """Buffer received data, find TERMINATOR, call handle_packet"""
        self.buffer.extend(data)
        while self.TERMINATOR in self.buffer:
            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
            self.handle_packet(packet)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

class FramedPacket(Protocol):
    """
    Read binary packets. Packets are expected to have a start and stop marker.

    The class also keeps track of the transport.
    """

    START = b'('
    STOP = b')'

    def __init__(self):
        self.packet = bytearray()
        self.in_packet = False
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        self.in_packet = False
        del self.packet[:]
        super(FramedPacket, self).connection_lost(exc)

    def data_received(self, data):
        """Find data enclosed in START/STOP, call handle_packet"""
        for byte in serial.iterbytes(data):
            if byte == self.START:
                self.in_packet = True
            elif byte == self.STOP:
                self.in_packet = False
                self.handle_packet(self.packet)
                del self.packet[:]
            elif self.in_packet:
                self.packet.append(byte)
            else:
                self.handle_out_of_packet_data(byte)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

    def handle_out_of_packet_data(self, data):
        """Process data that is received outside of packets"""
        pass

class LineReader(Packetizer):
    """
    Read and write (Unicode) lines from/to serial port.
    The encoding is applied.
    """

    TERMINATOR = b'\r\n'
    ENCODING = 'utf-8'
    UNICODE_HANDLING = 'replace'

    def handle_packet(self, packet):
        self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))

    def handle_line(self, line):
        """Process one line - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_line')

    def write_line(self, text):
        """
        Write text to the transport. ``text`` is a Unicode string and the encoding
        is applied before sending ans also the newline is append.
        """
        # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
        self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)

class ReaderThread(threading.Thread):
    """\
    Implement a serial port read loop and dispatch to a Protocol instance (like
    the asyncio.Protocol) but do it with threads.

    Calls to close() will close the serial port but it is also possible to just
    stop() this thread and continue the serial port instance otherwise.
    """

    def __init__(self, serial_instance, protocol_factory):
        """\
        Initialize thread.

        Note that the serial_instance' timeout is set to one second!
        Other settings are not changed.
        """
        super(ReaderThread, self).__init__()
        self.daemon = True
        self.serial = serial_instance
        self.protocol_factory = protocol_factory
        self.alive = True
        self._lock = threading.Lock()
        self._connection_made = threading.Event()
        self.protocol = None

    def stop(self):
        """Stop the reader thread"""
        self.alive = False
        if hasattr(self.serial, 'cancel_read'):
            self.serial.cancel_read()
        self.join(2)

    def run(self):
        """Reader loop"""
        if not hasattr(self.serial, 'cancel_read'):
            self.serial.timeout = 1
        self.protocol = self.protocol_factory()
        try:
            self.protocol.connection_made(self)
        except Exception as e:
            self.alive = False
            self.protocol.connection_lost(e)
            self._connection_made.set()
            return
        error = None
        self._connection_made.set()
        while self.alive and self.serial.is_open:
            try:
                # read all that is there or wait for one byte (blocking)
                data = self.serial.read(self.serial.in_waiting or 1)
            except serial.SerialException as e:
                # probably some I/O problem such as disconnected USB serial
                # adapters -> exit
                error = e
                break
            else:
                if data:
                    # make a separated try-except for called used code
                    try:
                        self.protocol.data_received(data)
                    except Exception as e:
                        error = e
                        break
        self.alive = False
        self.protocol.connection_lost(error)
        self.protocol = None

    def write(self, data):
        """Thread safe writing (uses lock)"""
        with self._lock:
            self.serial.write(data)

    def close(self):
        """Close the serial port and exit reader thread (uses lock)"""
        # use the lock to let other threads finish writing
        with self._lock:
            # first stop reading, so that closing can be done on idle port
            self.stop()
            self.serial.close()

    def connect(self):
        """
        Wait until connection is set up and return the transport and protocol
        instances.
        """
        if self.alive:
            self._connection_made.wait()
            if not self.alive:
                raise RuntimeError('connection_lost already called')
            return (self, self.protocol)
        else:
            raise RuntimeError('already stopped')

    # - -  context manager, returns protocol

    def __enter__(self):
        """\
        Enter context handler. May raise RuntimeError in case the connection
        could not be created.
        """
        self.start()
        self._connection_made.wait()
        if not self.alive:
            raise RuntimeError('connection_lost already called')
        return self.protocol

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Leave context: close port"""
        self.close()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# test
if __name__ == '__main__':
    # pylint: disable=wrong-import-position
    import sys
    import time
    import traceback

    #~ PORT = 'spy:///dev/ttyUSB0'
    PORT = 'loop://'

    class PrintLines(LineReader):
        def connection_made(self, transport):
            super(PrintLines, self).connection_made(transport)
            sys.stdout.write('port opened\n')
            self.write_line('hello world')

        def handle_line(self, data):
            sys.stdout.write('line received: {}\n'.format(repr(data)))

        def connection_lost(self, exc):
            if exc:
                traceback.print_exc(exc)
            sys.stdout.write('port closed\n')

    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
    with ReaderThread(ser, PrintLines) as protocol:
        protocol.write_line('hello')
        time.sleep(2)

    # alternative usage
    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
    t = ReaderThread(ser, PrintLines)
    t.start()
    transport, protocol = t.connect()
    protocol.write_line('hello')
    time.sleep(2)
    t.close()
<commit_msg>Fix "TypeError: an integer is required" error on Python 3.5.2<commit_after>#!/usr/bin/env python3
#
# Working with threading and pySerial
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C) 2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier:    BSD-3-Clause
"""\
Support threading with serial ports.
"""
import serial
import threading

class Protocol(object):
    """\
    Protocol as used by the ReaderThread. This base class provides empty
    implementations of all methods.
    """

    def connection_made(self, transport):
        """Called when reader thread is started"""

    def data_received(self, data):
        """Called with snippets received from the serial port"""

    def connection_lost(self, exc):
        """\
        Called when the serial port is closed or the reader loop terminated
        otherwise.
        """
        if isinstance(exc, Exception):
            raise exc

class Packetizer(Protocol):
    """
    Read binary packets from serial port. Packets are expected to be terminated
    with a TERMINATOR byte (null byte by default).

    The class also keeps track of the transport.
    """

    TERMINATOR = b'\0'

    def __init__(self):
        self.buffer = bytearray()
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        super(Packetizer, self).connection_lost(exc)

    def data_received(self, data):
        """Buffer received data, find TERMINATOR, call handle_packet"""
        self.buffer.extend(data)
        while self.TERMINATOR in self.buffer:
            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
            self.handle_packet(packet)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

class FramedPacket(Protocol):
    """
    Read binary packets. Packets are expected to have a start and stop marker.

    The class also keeps track of the transport.
    """

    START = b'('
    STOP = b')'

    def __init__(self):
        self.packet = bytearray()
        self.in_packet = False
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        self.in_packet = False
        del self.packet[:]
        super(FramedPacket, self).connection_lost(exc)

    def data_received(self, data):
        """Find data enclosed in START/STOP, call handle_packet"""
        for byte in serial.iterbytes(data):
            if byte == self.START:
                self.in_packet = True
            elif byte == self.STOP:
                self.in_packet = False
                self.handle_packet(self.packet)
                del self.packet[:]
            elif self.in_packet:
                self.packet.extend(byte)
            else:
                self.handle_out_of_packet_data(byte)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

    def handle_out_of_packet_data(self, data):
        """Process data that is received outside of packets"""
        pass

class LineReader(Packetizer):
    """
    Read and write (Unicode) lines from/to serial port.
    The encoding is applied.
    """

    TERMINATOR = b'\r\n'
    ENCODING = 'utf-8'
    UNICODE_HANDLING = 'replace'

    def handle_packet(self, packet):
        self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))

    def handle_line(self, line):
        """Process one line - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_line')

    def write_line(self, text):
        """
        Write text to the transport. ``text`` is a Unicode string and the encoding
        is applied before sending ans also the newline is append.
        """
        # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
        self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)

class ReaderThread(threading.Thread):
    """\
    Implement a serial port read loop and dispatch to a Protocol instance (like
    the asyncio.Protocol) but do it with threads.

    Calls to close() will close the serial port but it is also possible to just
    stop() this thread and continue the serial port instance otherwise.
    """

    def __init__(self, serial_instance, protocol_factory):
        """\
        Initialize thread.

        Note that the serial_instance' timeout is set to one second!
        Other settings are not changed.
        """
        super(ReaderThread, self).__init__()
        self.daemon = True
        self.serial = serial_instance
        self.protocol_factory = protocol_factory
        self.alive = True
        self._lock = threading.Lock()
        self._connection_made = threading.Event()
        self.protocol = None

    def stop(self):
        """Stop the reader thread"""
        self.alive = False
        if hasattr(self.serial, 'cancel_read'):
            self.serial.cancel_read()
        self.join(2)

    def run(self):
        """Reader loop"""
        if not hasattr(self.serial, 'cancel_read'):
            self.serial.timeout = 1
        self.protocol = self.protocol_factory()
        try:
            self.protocol.connection_made(self)
        except Exception as e:
            self.alive = False
            self.protocol.connection_lost(e)
            self._connection_made.set()
            return
        error = None
        self._connection_made.set()
        while self.alive and self.serial.is_open:
            try:
                # read all that is there or wait for one byte (blocking)
                data = self.serial.read(self.serial.in_waiting or 1)
            except serial.SerialException as e:
                # probably some I/O problem such as disconnected USB serial
                # adapters -> exit
                error = e
                break
            else:
                if data:
                    # make a separated try-except for called used code
                    try:
                        self.protocol.data_received(data)
                    except Exception as e:
                        error = e
                        break
        self.alive = False
        self.protocol.connection_lost(error)
        self.protocol = None

    def write(self, data):
        """Thread safe writing (uses lock)"""
        with self._lock:
            self.serial.write(data)

    def close(self):
        """Close the serial port and exit reader thread (uses lock)"""
        # use the lock to let other threads finish writing
        with self._lock:
            # first stop reading, so that closing can be done on idle port
            self.stop()
            self.serial.close()

    def connect(self):
        """
        Wait until connection is set up and return the transport and protocol
        instances.
        """
        if self.alive:
            self._connection_made.wait()
            if not self.alive:
                raise RuntimeError('connection_lost already called')
            return (self, self.protocol)
        else:
            raise RuntimeError('already stopped')

    # - -  context manager, returns protocol

    def __enter__(self):
        """\
        Enter context handler. May raise RuntimeError in case the connection
        could not be created.
        """
        self.start()
        self._connection_made.wait()
        if not self.alive:
            raise RuntimeError('connection_lost already called')
        return self.protocol

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Leave context: close port"""
        self.close()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# test
if __name__ == '__main__':
    # pylint: disable=wrong-import-position
    import sys
    import time
    import traceback

    #~ PORT = 'spy:///dev/ttyUSB0'
    PORT = 'loop://'

    class PrintLines(LineReader):
        def connection_made(self, transport):
            super(PrintLines, self).connection_made(transport)
            sys.stdout.write('port opened\n')
            self.write_line('hello world')

        def handle_line(self, data):
            sys.stdout.write('line received: {}\n'.format(repr(data)))

        def connection_lost(self, exc):
            if exc:
                traceback.print_exc(exc)
            sys.stdout.write('port closed\n')

    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
    with ReaderThread(ser, PrintLines) as protocol:
        protocol.write_line('hello')
        time.sleep(2)

    # alternative usage
    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
    t = ReaderThread(ser, PrintLines)
    t.start()
    transport, protocol = t.connect()
    protocol.write_line('hello')
    time.sleep(2)
    t.close()
<|endoftext|>


<commit_before>"""
.. module:: Grid
    :platform: Unix, Windows
    :synopsis: A simple control points grid generator to use with the nurbs.Surface module

.. moduleauthor:: Onur Rauf Bingol

"""

import math

class Grid:
    """ Simple 2D grid generator to generate control points grid input for nurbs.Surface module.

    This class is designed to be a **very simple** grid generator for generating input files for the :class:`.Surface` class.
    Currently, it is not a fully-featured grid generator which can fit any purpose, but as always, contributions are welcome! :-)
    """
    def __init__(self, size_x, size_y):
        """ Default constructor.

        :param size_x: width of the 2D grid
        :param size_y: heigth of the 2D grid
        """
        self._origin = [0.0, 0.0, 0.0]
        self._size_x = float(size_x)
        self._size_y = float(size_y)
        self._gridpts = []

    def grid(self):
        """ Returns the generated grid.

        .. note:: The format of the control points grid is described in `FORMATS.md <https://github.com/orbingol/NURBS-Python/blob/master/FORMATS.md>`_ file.

        :return: 2D list of points ([x,y,z]) in [u][v] format
        """
        return self._gridpts

    def generate(self, num_u, num_v):
        """ Generates the 2D grid.

        :param num_u: number of divisions in x-direction
        :param num_v: number of divisions in y-direction
        :return: None
        """
        # Some error checking and fixing
        if num_u < 1:
            raise ValueError("Divisions in the x-direction (num_u) cannot be less than 1!")

        if num_v < 1:
            raise ValueError("Divisions in the y-direction (num_v) cannot be less than 1!")

        if not isinstance(num_u, int):
            num_u = int(num_u)
            print("WARNING: Number of divisions must be an integer value. %d will be used as the value of num_u." % num_u)

        if not isinstance(num_v, int):
            num_v = int(num_v)
            print("WARNING: Number of divisions must be an integer value. %d will be used as the value of num_v." % num_v)

        # Set the number of divisions for each direction
        spacing_x = self._size_x / num_u
        spacing_y = self._size_y / num_v

        # Set initial position for x
        current_x = self._origin[0]

        # Start looping
        for u in range(0, num_u + 1):
            # Initialize a temporary list for storing the 3nd dimension
            row = []
            # Set initial position for y
            current_y = self._origin[1]
            for v in range(0, num_v + 1):
                # Add the first point
                row.append([current_x, current_y, 0.0])
                # Set the y value for the next row
                current_y = current_y + spacing_y
            # Update the list to be returned
            self._gridpts.append(row)
            # Set x the value for the next column
            current_x = current_x + spacing_x

    def rotate_z(self, angle=0):
        """ Rotates the grid about the z-axis.

        :param angle: angle of rotation about the z-axis
        :return: None
        """
        # Get current origin / starting point (we need a copy of the self._origin)
        current_origin = list(self._origin)

        # Translate to the origin
        self.translate([0.0, 0.0, 0.0])

        # Then, rotate about the z-axis
        rot = math.radians(angle)
        for r in self._gridpts:
            for c in r:
                new_x = (c[0] * math.cos(rot)) - (c[1] * math.sin(rot))
                new_y = (c[1] * math.cos(rot)) + (c[0] * math.sin(rot))
                c[0] = new_x
                c[1] = new_y

        # Finally, translate back to the starting location
        self.translate(current_origin)

    def translate(self, pt=(0.0, 0.0, 0.0)):
        """ Translates the grid origin to the input point.

        Grid origin is (0, 0, 0) at instantiation and always represents the bottom left corner of the 2D grid.

        :param pt: new origin point
        :return: None
        """
        # Find the difference between starting and the input point
        diff_x = pt[0] - self._origin[0]
        diff_y = pt[1] - self._origin[1]
        diff_z = pt[2] - self._origin[2]

        # Translate all points
        for r in self._gridpts:
            for c in r:
                c[0] = c[0] + diff_x
                c[1] = c[1] + diff_y
                c[2] = c[2] + diff_z

        # Update the origin (bottom left corner)
        self._origin = self._gridpts[0][0]

    def save(self, file_name="grid.txt"):
        """ Saves the generated grid to a text file.

        .. note:: The format of the text files is described in `FORMATS.md <https://github.com/orbingol/NURBS-Python/blob/master/FORMATS.md>`_ file.

        :param file_name: File name to be saved
        :return: None
        """
        # Some error checking
        if not self._gridpts:
            raise ValueError("Grid must be generated before saving it to a file!")

        if not isinstance(file_name, basestring):
            raise ValueError("File name must be a string!")

        # Open the file for writing
        target = open(file_name, 'w')
        # Clear file contents
        target.truncate()
        # Start saving the generated grid to the file
        for cols in self._gridpts:
            line = ""
            col_size = len(cols)
            counter = 0
            for rows in cols:
                line = line + str(rows[0]) + "," + str(rows[1]) + "," + str(rows[2])
                counter = counter + 1
                # Not the best way, but it works
                if counter != col_size:
                    line = line + ";"
            target.write(line)
            target.write("\n")

    def bumps(self, num_bumps):
        """ Generates random bumps (i.e. hills) on the 2D grid.

        .. note:: Still work in progress

        :param num_bumps: Number of bumps (i.e. hills) to be generated on the 2D grid
        :return: None
        """
        pass
<commit_msg>Updated bumps method in Grid class<commit_after>"""
.. module:: Grid
    :platform: Unix, Windows
    :synopsis: A simple control points grid generator to use with the nurbs.Surface module

.. moduleauthor:: Onur Rauf Bingol

"""

import sys
import math
import random

class Grid:
    """ Simple 2D grid generator to generate control points grid input for nurbs.Surface module.

    This class is designed to be a **very simple** grid generator for generating input files for the :class:`.Surface` class.
    Currently, it is not a fully-featured grid generator which can fit any purpose, but as always, contributions are welcome! :-)
    """
    def __init__(self, size_x, size_y):
        """ Default constructor.

        :param size_x: width of the 2D grid
        :param size_y: heigth of the 2D grid
        """
        self._origin = [0.0, 0.0, 0.0]
        self._size_x = float(size_x)
        self._size_y = float(size_y)
        self._gridpts = []

    def grid(self):
        """ Returns the generated grid.

        .. note:: The format of the control points grid is described in `FORMATS.md <https://github.com/orbingol/NURBS-Python/blob/master/FORMATS.md>`_ file.

        :return: 2D list of points ([x,y,z]) in [u][v] format
        """
        return self._gridpts

    def generate(self, num_u, num_v):
        """ Generates the 2D grid.

        :param num_u: number of divisions in x-direction
        :param num_v: number of divisions in y-direction
        :return: None
        """
        # Some error checking and fixing
        if num_u < 1:
            raise ValueError("Divisions in the x-direction (num_u) cannot be less than 1!")

        if num_v < 1:
            raise ValueError("Divisions in the y-direction (num_v) cannot be less than 1!")

        if not isinstance(num_u, int):
            num_u = int(num_u)
            print("WARNING: Number of divisions must be an integer value. %d will be used as the value of num_u." % num_u)

        if not isinstance(num_v, int):
            num_v = int(num_v)
            print("WARNING: Number of divisions must be an integer value. %d will be used as the value of num_v." % num_v)

        # Set the number of divisions for each direction
        spacing_x = self._size_x / num_u
        spacing_y = self._size_y / num_v

        # Set initial position for x
        current_x = self._origin[0]

        # Start looping
        for u in range(0, num_u + 1):
            # Initialize a temporary list for storing the 3nd dimension
            row = []
            # Set initial position for y
            current_y = self._origin[1]
            for v in range(0, num_v + 1):
                # Add the first point
                row.append([current_x, current_y, 0.0])
                # Set the y value for the next row
                current_y = current_y + spacing_y
            # Update the list to be returned
            self._gridpts.append(row)
            # Set x the value for the next column
            current_x = current_x + spacing_x

    def rotate_z(self, angle=0):
        """ Rotates the grid about the z-axis.

        :param angle: angle of rotation about the z-axis
        :return: None
        """
        # Get current origin / starting point (we need a copy of the self._origin)
        current_origin = list(self._origin)

        # Translate to the origin
        self.translate([0.0, 0.0, 0.0])

        # Then, rotate about the z-axis
        rot = math.radians(angle)
        for r in self._gridpts:
            for c in r:
                new_x = (c[0] * math.cos(rot)) - (c[1] * math.sin(rot))
                new_y = (c[1] * math.cos(rot)) + (c[0] * math.sin(rot))
                c[0] = new_x
                c[1] = new_y

        # Finally, translate back to the starting location
        self.translate(current_origin)

    def translate(self, pt=(0.0, 0.0, 0.0)):
        """ Translates the grid origin to the input point.

        Grid origin is (0, 0, 0) at instantiation and always represents the bottom left corner of the 2D grid.

        :param pt: new origin point
        :return: None
        """
        # Find the difference between starting and the input point
        diff_x = pt[0] - self._origin[0]
        diff_y = pt[1] - self._origin[1]
        diff_z = pt[2] - self._origin[2]

        # Translate all points
        for r in self._gridpts:
            for c in r:
                c[0] = c[0] + diff_x
                c[1] = c[1] + diff_y
                c[2] = c[2] + diff_z

        # Update the origin (bottom left corner)
        self._origin = self._gridpts[0][0]

    def save(self, file_name="grid.txt"):
        """ Saves the generated grid to a text file.

        .. note:: The format of the text files is described in `FORMATS.md <https://github.com/orbingol/NURBS-Python/blob/master/FORMATS.md>`_ file.

        :param file_name: File name to be saved
        :return: None
        """
        # Some error checking
        if not self._gridpts:
            raise ValueError("Grid must be generated before saving it to a file!")

        if not isinstance(file_name, basestring):
            raise ValueError("File name must be a string!")

        # Open the file for writing
        target = open(file_name, 'w')
        # Clear file contents
        target.truncate()
        # Start saving the generated grid to the file
        for cols in self._gridpts:
            line = ""
            col_size = len(cols)
            counter = 0
            for rows in cols:
                line = line + str(rows[0]) + "," + str(rows[1]) + "," + str(rows[2])
                counter = counter + 1
                # Not the best way, but it works
                if counter != col_size:
                    line = line + ";"
            target.write(line)
            target.write("\n")

    def bumps(self, num_bumps=0, all_positive=False, bump_height=3):
        """ Generates random bumps (i.e. hills) on the 2D grid.

        This method generates hills on the grid defined by the **num_bumps** parameter. The direction of the generated hills
        are chosen randomly by default, but this behavior can be controlled by **all_positive** parameter. It is also
        possible to control the z-value using **bump_height** parameter.

        Please note that, not all grids can be modified to have **num_bumps** number of bumps. Therefore, this function
        uses a trial-and-error method to determine whether the bumps can be generatable or not. For instance;

          >> testgrid = Grid(5,10) # generates a 5x10 rectangle
          >> testgrid.generate(2, 2) # splits the rectangle into 4 pieces
          >> testgrid.bumps(100) # impossible, it will return an error message
          >> testgrid.bumps(1) # possible, you will get a bump in the middle of the generated grid

        :param num_bumps: Number of bumps (i.e. hills) to be generated on the 2D grid
        :param all_positive: Generate all bumps on the positive z direction
        :param bump_height: z-value of the generated bumps on the grid
        :return: None
        """
        # Some error checking
        if num_bumps < 0:
            print("No bumps are generated!")
            return

        if bump_height < 0:
            raise ValueError("Height must be a positive number")

        if not isinstance(all_positive, bool):
            raise ValueError("all_positive must be a boolean value!")

        # Initialize a list to store bumps
        bump_list = []

        # Find size of the grid
        len_u = len(self._gridpts)
        len_v = len(self._gridpts[0])

        # Choose u and v positions inside the grid (i.e. not on the edges)
        for nb in range(1, num_bumps):
            trials = 0
            while trials < 10:
                u = random.randint(1, len_u-2)
                v = random.randint(1, len_v-2)
                temp = [u, v]
                if not bump_list:
                    bump_list.append(temp)
                else:
                    if self._check_bump(bump_list, temp):
                        bump_list.append(temp)
                        trials = 100  # set number of trials to a big value
                        break
                    else:
                        trials = trials + 1
            if trials == 10:
                print("Cannot generate %d bumps on this grid." % num_bumps)
                print("You might need to generate a grid with more divisions.")
                sys.exit(0)

        # Update the grid with the bumps
        for u, v in bump_list:
            # Toss a coin to find the bump direction
            if all_positive:
                roll = 1
            else:
                roll = random.randint(0, 1)
            if roll:
                z_val = float(bump_height)
            else:
                z_val = float(-1 * bump_height)

            # Update the grid points
            self._gridpts[u - 1][v - 1][2] = z_val / 2.0
            self._gridpts[u - 1][v][2] = z_val / 2.0
            self._gridpts[u - 1][v + 1][2] = z_val / 2.0
            self._gridpts[u][v - 1][2] = z_val / 2.0
            self._gridpts[u][v][2] = z_val
            self._gridpts[u][v + 1][2] = z_val / 2.0
            self._gridpts[u + 1][v - 1][2] = z_val / 2.0
            self._gridpts[u + 1][v][2] = z_val / 2.0
            self._gridpts[u + 1][v + 1][2] = z_val / 2.0

    def _check_bump(self, uv_list=(), to_be_checked_uv=(0, 0)):
        # If input list is empty, return true
        if not uv_list:
            return True

        # Check the input point or its surroundings are close to the existing ones
        for uv in uv_list:
            u = to_be_checked_uv[0]
            v = to_be_checked_uv[1]
            check_list = [
                [u - 1, v - 1],
                [u - 1, v],
                [u - 1, v + 1],
                [u, v - 1],
                [u, v],
                [u, v + 1],
                [u + 1, v - 1],
                [u + 1, v],
                [u + 1, v + 1],
            ]
            for check in check_list:
                if uv == check:
                    return False

        # Otherwise, return true
        return True
<|endoftext|>


<commit_before>#!/usr/bin/env python3
#
# Working with threading and pySerial
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C) 2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier:    BSD-3-Clause
"""\
Support threading with serial ports.
"""
import serial
import threading

class Protocol(object):
    """\
    Protocol as used by the ReaderThread. This base class provides empty
    implementations of all methods.
    """

    def connection_made(self, transport):
        """Called when reader thread is started"""

    def data_received(self, data):
        """Called with snippets received from the serial port"""

    def connection_lost(self, exc):
        """\
        Called when the serial port is closed or the reader loop terminated
        otherwise.
        """

class Packetizer(Protocol):
    """
    Read binary packets from serial port. Packets are expected to be terminated
    with a TERMINATOR byte (null byte by default).

    The class also keeps track of the transport.
    """

    TERMINATOR = b'\0'

    def __init__(self):
        self.buffer = bytearray()
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None

    def data_received(self, data):
        """Buffer received data, find TERMINATOR, call handle_packet"""
        self.buffer.extend(data)
        while self.TERMINATOR in self.buffer:
            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
            self.handle_packet(packet)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

class FramedPacket(Protocol):
    """
    Read binary packets. Packets are expected to have a start and stop marker.

    The class also keeps track of the transport.
    """

    START = b'('
    STOP = b')'

    def __init__(self):
        self.packet = bytearray()
        self.in_packet = False
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        self.in_packet = False
        del self.packet[:]

    def data_received(self, data):
        """Find data enclosed in START/STOP, call handle_packet"""
        for byte in serial.iterbytes(data):
            if byte == self.START:
                self.in_packet = True
            elif byte == self.STOP:
                self.in_packet = False
                self.handle_packet(packet)
                del self.packet[:]
            elif self.in_packet:
                self.packet.append(byte)
            else:
                self.handle_out_of_packet_data(byte)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

    def handle_out_of_packet_data(self, data):
        """Process data that is received outside of packets"""
        pass

class LineReader(Packetizer):
    """
    Read and write (Unicode) lines from/to serial port.
    The encoding is applied.
    """

    TERMINATOR = b'\r\n'
    ENCODING = 'utf-8'
    UNICODE_HANDLING = 'replace'

    def handle_packet(self, packet):
        self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))

    def handle_line(self, line):
        """Process one line - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_line')

    def write_line(self, text):
        """
        Write text to the transport. ``text`` is a Unicode string and the encoding
        is applied before sending ans also the newline is append.
        """
        # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
        self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)

class ReaderThread(threading.Thread):
    """\
    Implement a serial port read loop and dispatch to a Protocol instance (like
    the asyncio.Protocol) but do it with threads.

    Calls to close() will close the serial port but it is also possible to just
    stop() this thread and continue the serial port instance otherwise.
    """

    def __init__(self, serial_instance, protocol_factory):
        """\
        Initialize thread.

        Note that the serial_instance' timeout is set to one second!
        Other settings are not changed.
        """
        super(ReaderThread, self).__init__()
        self.daemon = True
        self.serial = serial_instance
        self.protocol_factory = protocol_factory
        self.alive = True
        self._lock = threading.Lock()
        self._connection_made = threading.Event()
        self.protocol = None

    def stop(self):
        """Stop the reader thread"""
        self.alive = False
        self.join(2)

    def run(self):
        """Reader loop"""
        self.serial.timeout = 1
        self.protocol = self.protocol_factory()
        try:
            self.protocol.connection_made(self)
        except Exception as e:
            self.alive = False
            self.protocol.connection_lost(e)
            self._connection_made.set()
            return
        error = None
        self._connection_made.set()
        while self.alive and self.serial.is_open:
            try:
                # read all that is there or wait for one byte (blocking)
                data = self.serial.read(self.serial.in_waiting or 1)
            except serial.SerialException as e:
                # probably some I/O problem such as disconnected USB serial
                # adapters -> exit
                error = e
                break
            else:
                if data:
                    # make a separated try-except for called used code
                    try:
                        self.protocol.data_received(data)
                    except Exception as e:
                        error = e
                        break
        self.alive = False
        self.protocol.connection_lost(error)
        self.protocol = None

    def write(self, data):
        """Thread safe writing (uses lock)"""
        with self._lock:
            self.serial.write(data)

    def close(self):
        """Close the serial port and exit reader thread (uses lock)"""
        # use the lock to let other threads finish writing
        with self._lock:
            # first stop reading, so that closing can be done on idle port
            self.stop()
            self.serial.close()

    def connect(self):
        """
        Wait until connection is set up and return the transport and protocol
        instances.
        """
        if self.alive:
            self._connection_made.wait()
            if not self.alive:
                raise RuntimeError('connection_lost already called')
            return (self, self.protocol)
        else:
            raise RuntimeError('already stopped')

    # - -  context manager, returns protocol

    def __enter__(self):
        """\
        Enter context handler. May raise RuntimeError in case the connection
        could not be created.
        """
        self.start()
        self._connection_made.wait()
        if not self.alive:
            raise RuntimeError('connection_lost already called')
        return self.protocol

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Leave context: close port"""
        self.close()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# test
if __name__ == '__main__':
    # pylint: disable=wrong-import-position
    import sys
    import time
    import traceback

    class PrintLines(LineReader):
        def connection_made(self, transport):
            super(PrintLines, self).connection_made(transport)
            sys.stdout.write('port opened\n')
            self.write_line('hello world')

        def handle_line(self, data):
            sys.stdout.write('line received: {}\n'.format(repr(data)))

        def connection_lost(self, exc):
            if exc:
                traceback.print_exc(exc)
            sys.stdout.write('port closed\n')

    ser = serial.serial_for_url('loop://', baudrate=115200, timeout=1)
    with ReaderThread(ser, PrintLines) as protocol:
        protocol.write_line('hello')
        time.sleep(2)

    # alternative usage
    ser = serial.serial_for_url('loop://', baudrate=115200, timeout=1)
    t = ReaderThread(ser, PrintLines)
    t.start()
    transport, protocol = t.connect()
    protocol.write_line('hello')
    time.sleep(2)
    t.close()
<commit_msg>raise exceptions in connection_lost<commit_after>#!/usr/bin/env python3
#
# Working with threading and pySerial
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C) 2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier:    BSD-3-Clause
"""\
Support threading with serial ports.
"""
import serial
import threading

class Protocol(object):
    """\
    Protocol as used by the ReaderThread. This base class provides empty
    implementations of all methods.
    """

    def connection_made(self, transport):
        """Called when reader thread is started"""

    def data_received(self, data):
        """Called with snippets received from the serial port"""

    def connection_lost(self, exc):
        """\
        Called when the serial port is closed or the reader loop terminated
        otherwise.
        """
        if isinstance(exc, Exception):
            raise exc

class Packetizer(Protocol):
    """
    Read binary packets from serial port. Packets are expected to be terminated
    with a TERMINATOR byte (null byte by default).

    The class also keeps track of the transport.
    """

    TERMINATOR = b'\0'

    def __init__(self):
        self.buffer = bytearray()
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        super().connection_lost(exc)

    def data_received(self, data):
        """Buffer received data, find TERMINATOR, call handle_packet"""
        self.buffer.extend(data)
        while self.TERMINATOR in self.buffer:
            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
            self.handle_packet(packet)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

class FramedPacket(Protocol):
    """
    Read binary packets. Packets are expected to have a start and stop marker.

    The class also keeps track of the transport.
    """

    START = b'('
    STOP = b')'

    def __init__(self):
        self.packet = bytearray()
        self.in_packet = False
        self.transport = None

    def connection_made(self, transport):
        """Store transport"""
        self.transport = transport

    def connection_lost(self, exc):
        """Forget transport"""
        self.transport = None
        self.in_packet = False
        del self.packet[:]
        super().connection_lost(exc)

    def data_received(self, data):
        """Find data enclosed in START/STOP, call handle_packet"""
        for byte in serial.iterbytes(data):
            if byte == self.START:
                self.in_packet = True
            elif byte == self.STOP:
                self.in_packet = False
                self.handle_packet(packet)
                del self.packet[:]
            elif self.in_packet:
                self.packet.append(byte)
            else:
                self.handle_out_of_packet_data(byte)

    def handle_packet(self, packet):
        """Process packets - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_packet')

    def handle_out_of_packet_data(self, data):
        """Process data that is received outside of packets"""
        pass

class LineReader(Packetizer):
    """
    Read and write (Unicode) lines from/to serial port.
    The encoding is applied.
    """

    TERMINATOR = b'\r\n'
    ENCODING = 'utf-8'
    UNICODE_HANDLING = 'replace'

    def handle_packet(self, packet):
        self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))

    def handle_line(self, line):
        """Process one line - to be overridden by subclassing"""
        raise NotImplementedError('please implement functionality in handle_line')

    def write_line(self, text):
        """
        Write text to the transport. ``text`` is a Unicode string and the encoding
        is applied before sending ans also the newline is append.
        """
        # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
        self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)

class ReaderThread(threading.Thread):
    """\
    Implement a serial port read loop and dispatch to a Protocol instance (like
    the asyncio.Protocol) but do it with threads.

    Calls to close() will close the serial port but it is also possible to just
    stop() this thread and continue the serial port instance otherwise.
    """

    def __init__(self, serial_instance, protocol_factory):
        """\
        Initialize thread.

        Note that the serial_instance' timeout is set to one second!
        Other settings are not changed.
        """
        super(ReaderThread, self).__init__()
        self.daemon = True
        self.serial = serial_instance
        self.protocol_factory = protocol_factory
        self.alive = True
        self._lock = threading.Lock()
        self._connection_made = threading.Event()
        self.protocol = None

    def stop(self):
        """Stop the reader thread"""
        self.alive = False
        self.join(2)

    def run(self):
        """Reader loop"""
        self.serial.timeout = 1
        self.protocol = self.protocol_factory()
        try:
            self.protocol.connection_made(self)
        except Exception as e:
            self.alive = False
            self.protocol.connection_lost(e)
            self._connection_made.set()
            return
        error = None
        self._connection_made.set()
        while self.alive and self.serial.is_open:
            try:
                # read all that is there or wait for one byte (blocking)
                data = self.serial.read(self.serial.in_waiting or 1)
            except serial.SerialException as e:
                # probably some I/O problem such as disconnected USB serial
                # adapters -> exit
                error = e
                break
            else:
                if data:
                    # make a separated try-except for called used code
                    try:
                        self.protocol.data_received(data)
                    except Exception as e:
                        error = e
                        break
        self.alive = False
        self.protocol.connection_lost(error)
        self.protocol = None

    def write(self, data):
        """Thread safe writing (uses lock)"""
        with self._lock:
            self.serial.write(data)

    def close(self):
        """Close the serial port and exit reader thread (uses lock)"""
        # use the lock to let other threads finish writing
        with self._lock:
            # first stop reading, so that closing can be done on idle port
            self.stop()
            self.serial.close()

    def connect(self):
        """
        Wait until connection is set up and return the transport and protocol
        instances.
        """
        if self.alive:
            self._connection_made.wait()
            if not self.alive:
                raise RuntimeError('connection_lost already called')
            return (self, self.protocol)
        else:
            raise RuntimeError('already stopped')

    # - -  context manager, returns protocol

    def __enter__(self):
        """\
        Enter context handler. May raise RuntimeError in case the connection
        could not be created.
        """
        self.start()
        self._connection_made.wait()
        if not self.alive:
            raise RuntimeError('connection_lost already called')
        return self.protocol

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Leave context: close port"""
        self.close()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# test
if __name__ == '__main__':
    # pylint: disable=wrong-import-position
    import sys
    import time
    import traceback

    class PrintLines(LineReader):
        def connection_made(self, transport):
            super(PrintLines, self).connection_made(transport)
            sys.stdout.write('port opened\n')
            self.write_line('hello world')

        def handle_line(self, data):
            sys.stdout.write('line received: {}\n'.format(repr(data)))

        def connection_lost(self, exc):
            if exc:
                traceback.print_exc(exc)
            sys.stdout.write('port closed\n')

    ser = serial.serial_for_url('loop://', baudrate=115200, timeout=1)
    with ReaderThread(ser, PrintLines) as protocol:
        protocol.write_line('hello')
        time.sleep(2)

    # alternative usage
    ser = serial.serial_for_url('loop://', baudrate=115200, timeout=1)
    t = ReaderThread(ser, PrintLines)
    t.start()
    transport, protocol = t.connect()
    protocol.write_line('hello')
    time.sleep(2)
    t.close()
<|endoftext|>