<commit_before>#!/usr/bin/env python
#
#    path_validator_path.py: unit tests for vyconf.tree.referencetree.PathValidator
#    Copyright (C) 2014 VyOS Development Group <maintainers@vyos.net>
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#    USA

import os
import vyconf.tree.referencetree as reftree
import vyconf.types as types
import vyconf.types.dummy as dummytypes
import unittest

import pkg_resources

class TestVytreePathValidator(unittest.TestCase):
    def setUp(self):
        self.types_dict = types.get_types(dummytypes)

        self.reference_tree = reftree.ReferenceNode('root')
        data_dir = os.environ["VYCONF_DATA_DIR"]
        test_data_dir = os.environ["VYCONF_TEST_DATA_DIR"]
        xml_file = os.path.join(test_data_dir, "interface_definition_validation_test.xml")
        print xml_file
        schema_file = os.path.join(data_dir, "schemata", "interface_definition.rng")
        loader = reftree.ReferenceTreeLoader(xml_file, self.types_dict, schema=schema_file)
        loader.load(self.reference_tree)

        self.validator = reftree.PathValidator(self.types_dict, self.reference_tree)

    def test_path_exists(self):
        self.validator.validate(['foo', 'bar', 'somename'])

    def test_path_doesnt_exist(self):
        self.assertRaises(reftree.PathValidationError, self.validator.validate, ['foo', 'baz'])

    def test_leaf_node_valid_value(self):
       self.validator.validate(['quux', 'spam', 'somevalue'])
<commit_msg>Remove stray debug print from path validator test.<commit_after>#!/usr/bin/env python
#
#    path_validator_path.py: unit tests for vyconf.tree.referencetree.PathValidator
#    Copyright (C) 2014 VyOS Development Group <maintainers@vyos.net>
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#    USA

import os
import vyconf.tree.referencetree as reftree
import vyconf.types as types
import vyconf.types.dummy as dummytypes
import unittest

import pkg_resources

class TestVytreePathValidator(unittest.TestCase):
    def setUp(self):
        self.types_dict = types.get_types(dummytypes)

        self.reference_tree = reftree.ReferenceNode('root')
        data_dir = os.environ["VYCONF_DATA_DIR"]
        test_data_dir = os.environ["VYCONF_TEST_DATA_DIR"]
        xml_file = os.path.join(test_data_dir, "interface_definition_validation_test.xml")
        schema_file = os.path.join(data_dir, "schemata", "interface_definition.rng")
        loader = reftree.ReferenceTreeLoader(xml_file, self.types_dict, schema=schema_file)
        loader.load(self.reference_tree)

        self.validator = reftree.PathValidator(self.types_dict, self.reference_tree)

    def test_path_exists(self):
        self.validator.validate(['foo', 'bar', 'somename'])

    def test_path_doesnt_exist(self):
        self.assertRaises(reftree.PathValidationError, self.validator.validate, ['foo', 'baz'])

    def test_leaf_node_valid_value(self):
       self.validator.validate(['quux', 'spam', 'somevalue'])
<|endoftext|>


<commit_before>from datetime import date

from django.test import TestCase
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.db import IntegrityError

from registration.models import Tournament
from capitalism.models import TournamentProduct

class TournamentProductTestCase(TestCase):
    def setUp(self):
        tourney = Tournament.utilities.create_tournament(
            title='Tournoi test',
            game='2X',
            date=date.today(),
            nb_max=64,
        )
        TournamentProduct.utilities.create_tournament_product(
            tournament=tourney,
            price=10.50
        )

    def test_creation_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        self.assertIsInstance(tourney_product, TournamentProduct)

    def test_create_tournament_product_only_existing_tournament(self):
        tourney = Tournament(title='LoL',
                             game='2X',
                             date=date.today(),
                             nb_max=16)
        self.assertRaises(IntegrityError,
                          TournamentProduct.utilities.create_tournament_product,
                          tournament=tourney,
                          price=5.25)

    def test_tournament_product_too_much_stock(self):
        tourney = Tournament.objects.get(title='Tournoi test')
        self.assertRaises(ValidationError,
                          TournamentProduct.utilities.create_tournament_product,
                          tournament=tourney,
                          price=15,
                          stock=65
                          )
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        self.assertRaises(ValidationError,
                          TournamentProduct.utilities.update_tournament_product,
                          product_id=tourney_product.pk,
                          stock=65
                          )

    def test_update_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        TournamentProduct.utilities.update_tournament_product(
            product_id=tourney_product.pk,
            price=12
            )
        # Updated product exists in the database
        tourney_product = \
            TournamentProduct.objects.get(price=12)
        self.assertIsInstance(tourney_product, TournamentProduct)
        # Old one does not
        self.assertRaises(ObjectDoesNotExist,
                          TournamentProduct.objects.get,
                          price=10.50)

    def test_delete_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        self.assertIsInstance(tourney_product, TournamentProduct)
        TournamentProduct.utilities.delete_tournament_product(tourney_product.pk)
        self.assertRaises(ObjectDoesNotExist,
                          TournamentProduct.objects.get,
                          price=10.50)
<commit_msg>comments<commit_after>from datetime import date

from django.test import TestCase
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.db import IntegrityError

from registration.models import Tournament
from capitalism.models import TournamentProduct

class TournamentProductTestCase(TestCase):
    def setUp(self):
        tourney = Tournament.utilities.create_tournament(
            title='Tournoi test',
            game='2X',
            date=date.today(),
            nb_max=64,
        )
        TournamentProduct.utilities.create_tournament_product(
            tournament=tourney,
            price=10.50
        )

    def test_creation_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        self.assertIsInstance(tourney_product, TournamentProduct)

    def test_create_tournament_product_only_existing_tournament(self):
        tourney = Tournament(title='LoL',
                             game='2X',
                             date=date.today(),
                             nb_max=16)
        self.assertRaises(IntegrityError,
                          TournamentProduct.utilities.create_tournament_product,
                          tournament=tourney,
                          price=5.25)

    def test_tournament_product_too_much_stock(self):
        """A tournament product cannot have more stock than available spots"""
        tourney = Tournament.objects.get(title='Tournoi test')
        # Test on creation of a new tournament product
        self.assertRaises(ValidationError,
                          TournamentProduct.utilities.create_tournament_product,
                          tournament=tourney,
                          price=15,
                          stock=65
                          )
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        # Test on updating an existing tournament product
        self.assertRaises(ValidationError,
                          TournamentProduct.utilities.update_tournament_product,
                          product_id=tourney_product.pk,
                          stock=65
                          )

    def test_update_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        TournamentProduct.utilities.update_tournament_product(
            product_id=tourney_product.pk,
            price=12
            )
        # Updated product exists in the database
        tourney_product = \
            TournamentProduct.objects.get(price=12)
        self.assertIsInstance(tourney_product, TournamentProduct)
        # Old one does not
        self.assertRaises(ObjectDoesNotExist,
                          TournamentProduct.objects.get,
                          price=10.50)

    def test_delete_tournament_product(self):
        tourney_product = \
            TournamentProduct.objects.get(price=10.50)
        self.assertIsInstance(tourney_product, TournamentProduct)
        TournamentProduct.utilities.delete_tournament_product(tourney_product.pk)
        self.assertRaises(ObjectDoesNotExist,
                          TournamentProduct.objects.get,
                          price=10.50)
<|endoftext|>


<commit_before># Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from spack import *

class OpenpmdApi(CMakePackage):
    """C++ & Python API for Scientific I/O"""

    homepage = "http://www.openPMD.org"
    url      = "https://github.com/openPMD/openPMD-api/archive/0.13.3.tar.gz"
    git      = "https://github.com/openPMD/openPMD-api.git"

    maintainers = ['ax3l']

    # C++14 up until here
    version('develop', branch='dev')
    version('0.14.0', sha256='7bb561c1a6f54e9a6a1b56aaf1d4d098bbe290d204f84ebe5a6f11b3cab2be6e')
    #   temporary, pre 0.14.0 version for HiPACE++
    version('hipace', commit='ac083025ee662469b8cad1adf93eef48cde35f58')
    version('0.13.4', preferred=True,
            sha256='46c013be5cda670f21969675ce839315d4f5ada0406a6546a91ec3441402cf5e')
    version('0.13.3', sha256='4b8f84bd89cd540c73ffe8c21085970453cb7f0e4f125f11a4e288433f64b58c')
    version('0.13.2', sha256='2e5170d41bb7b2c0608ec833eee7f9adf8175b46734743f6e46dcce6f6685fb0')
    version('0.13.1', sha256='81ff79419982eb1b0865d1736f73f950f5d4c356d3c78200ceeab7f54dc07fd7')
    version('0.13.0', sha256='97c2e43d80ee5c5288f278bd54f0dcb40e7f48a575b278fcef9660214b779bb0')
    # C++11 up until here
    version('0.12.0',  tag='0.12.0-alpha')
    version('0.11.1',  tag='0.11.1-alpha')

    variant('shared', default=True,
            description='Build a shared version of the library')
    variant('mpi', default=True,
            description='Enable parallel I/O')
    variant('hdf5', default=True,
            description='Enable HDF5 support')
    variant('adios1', default=False,
            description='Enable ADIOS1 support')
    variant('adios2', default=True,
            description='Enable ADIOS2 support')
    variant('python', default=False,
            description='Enable Python bindings')

    depends_on('cmake@3.15.0:', type='build')
    depends_on('mpark-variant@1.4.0:')
    depends_on('catch2@2.6.1:', type='test')
    depends_on('catch2@2.13.4:', type='test', when='@0.14.0:')
    depends_on('mpi@2.3:', when='+mpi')  # might become MPI 3.0+
    depends_on('hdf5@1.8.13:', when='+hdf5')
    depends_on('hdf5@1.8.13: ~mpi', when='~mpi +hdf5')
    depends_on('hdf5@1.8.13: +mpi', when='+mpi +hdf5')
    depends_on('adios@1.13.1: ~sz', when='+adios1')
    depends_on('adios@1.13.1: ~mpi ~sz', when='~mpi +adios1')
    depends_on('adios@1.13.1: +mpi ~sz', when='+mpi +adios1')
    depends_on('adios2@2.5.0:', when='+adios2')
    depends_on('adios2@2.6.0:', when='+adios2 @0.12.0:')
    depends_on('adios2@2.7.0:', when='+adios2 @0.14.0:')
    depends_on('adios2@2.5.0: ~mpi', when='~mpi +adios2')
    depends_on('adios2@2.5.0: +mpi', when='+mpi +adios2')
    depends_on('nlohmann-json@3.9.1:')
    depends_on('py-pybind11@2.6.2:', when='+python', type='link')
    depends_on('py-numpy@1.15.1:', when='+python', type=['test', 'run'])
    depends_on('py-mpi4py@2.1.0:', when='+python +mpi', type=['test', 'run'])
    depends_on('python@3.6:', when='+python', type=['link', 'test', 'run'])

    conflicts('^hdf5 api=v16', msg='openPMD-api requires HDF5 APIs for 1.8+')

    # Fix breaking HDF5 1.12.0 API when build with legacy api options
    # https://github.com/openPMD/openPMD-api/pull/1012
    patch('hdf5-1.12.0.patch', when='@:0.13.99 +hdf5')

    extends('python', when='+python')

    def cmake_args(self):
        spec = self.spec

        args = [
            self.define_from_variant('BUILD_SHARED_LIBS', 'shared'),
            # variants
            self.define_from_variant('openPMD_USE_MPI', 'mpi'),
            self.define_from_variant('openPMD_USE_HDF5', 'hdf5'),
            self.define_from_variant('openPMD_USE_ADIOS1', 'adios1'),
            self.define_from_variant('openPMD_USE_ADIOS2', 'adios2'),
            self.define_from_variant('openPMD_USE_PYTHON', 'python'),
            # tests and examples
            self.define('BUILD_TESTING', self.run_tests),
            self.define('BUILD_EXAMPLES', self.run_tests)
        ]

        # switch internally shipped third-party libraries for spack
        if spec.satisfies('+python'):
            py_exe_define = 'Python_EXECUTABLE' \
                if spec.version >= Version('0.13.0') else 'PYTHON_EXECUTABLE'
            args += [
                self.define(py_exe_define, self.spec['python'].command.path),
                self.define('openPMD_USE_INTERNAL_PYBIND11', False)
            ]

        args += [
            self.define('openPMD_USE_INTERNAL_JSON', False),
            self.define('openPMD_USE_INTERNAL_VARIANT', False)
        ]
        if self.run_tests:
            args.append(self.define('openPMD_USE_INTERNAL_CATCH', False))

        return args

    def setup_run_environment(self, env):
        spec = self.spec
        # pre-load dependent CMake-PUBLIC header-only libs
        env.prepend_path('CMAKE_PREFIX_PATH', spec['mpark-variant'].prefix)
        env.prepend_path('CPATH', spec['mpark-variant'].prefix.include)

        # more deps searched in openPMDConfig.cmake
        if spec.satisfies("+mpi"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['mpi'].prefix)
        if spec.satisfies("+adios1"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['adios'].prefix)
            env.prepend_path('PATH', spec['adios'].prefix.bin)  # adios-config
        if spec.satisfies("+adios2"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['adios2'].prefix)
        if spec.satisfies("+hdf5"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['hdf5'].prefix)

    def setup_dependent_build_environment(self, env, dependent_spec):
        # pre-load dependent CMake-PUBLIC header-only libs
        env.prepend_path('CMAKE_PREFIX_PATH',
                         self.spec['mpark-variant'].prefix)
        env.prepend_path('CPATH', self.spec['mpark-variant'].prefix.include)
<commit_msg>openPMD-api: 0.14.1 (#25265)<commit_after># Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from spack import *

class OpenpmdApi(CMakePackage):
    """C++ & Python API for Scientific I/O"""

    homepage = "http://www.openPMD.org"
    url      = "https://github.com/openPMD/openPMD-api/archive/0.13.3.tar.gz"
    git      = "https://github.com/openPMD/openPMD-api.git"

    maintainers = ['ax3l']

    # C++14 up until here
    version('develop', branch='dev')
    version('0.14.1', sha256='172fd1d785627d01c77f1170adc5a18bd8a6302e804d0f271dc0d616a5156791')
    version('0.14.0', sha256='7bb561c1a6f54e9a6a1b56aaf1d4d098bbe290d204f84ebe5a6f11b3cab2be6e')
    #   temporary, pre 0.14.0 version for HiPACE++
    version('hipace', commit='ac083025ee662469b8cad1adf93eef48cde35f58')
    version('0.13.4', preferred=True,
            sha256='46c013be5cda670f21969675ce839315d4f5ada0406a6546a91ec3441402cf5e')
    version('0.13.3', sha256='4b8f84bd89cd540c73ffe8c21085970453cb7f0e4f125f11a4e288433f64b58c')
    version('0.13.2', sha256='2e5170d41bb7b2c0608ec833eee7f9adf8175b46734743f6e46dcce6f6685fb0')
    version('0.13.1', sha256='81ff79419982eb1b0865d1736f73f950f5d4c356d3c78200ceeab7f54dc07fd7')
    version('0.13.0', sha256='97c2e43d80ee5c5288f278bd54f0dcb40e7f48a575b278fcef9660214b779bb0')
    # C++11 up until here
    version('0.12.0',  tag='0.12.0-alpha')
    version('0.11.1',  tag='0.11.1-alpha')

    variant('shared', default=True,
            description='Build a shared version of the library')
    variant('mpi', default=True,
            description='Enable parallel I/O')
    variant('hdf5', default=True,
            description='Enable HDF5 support')
    variant('adios1', default=False,
            description='Enable ADIOS1 support')
    variant('adios2', default=True,
            description='Enable ADIOS2 support')
    variant('python', default=False,
            description='Enable Python bindings')

    depends_on('cmake@3.15.0:', type='build')
    depends_on('mpark-variant@1.4.0:')
    depends_on('catch2@2.6.1:', type='test')
    depends_on('catch2@2.13.4:', type='test', when='@0.14.0:')
    depends_on('mpi@2.3:', when='+mpi')  # might become MPI 3.0+
    depends_on('hdf5@1.8.13:', when='+hdf5')
    depends_on('hdf5@1.8.13: ~mpi', when='~mpi +hdf5')
    depends_on('hdf5@1.8.13: +mpi', when='+mpi +hdf5')
    depends_on('adios@1.13.1: ~sz', when='+adios1')
    depends_on('adios@1.13.1: ~mpi ~sz', when='~mpi +adios1')
    depends_on('adios@1.13.1: +mpi ~sz', when='+mpi +adios1')
    depends_on('adios2@2.5.0:', when='+adios2')
    depends_on('adios2@2.6.0:', when='+adios2 @0.12.0:')
    depends_on('adios2@2.7.0:', when='+adios2 @0.14.0:')
    depends_on('adios2@2.5.0: ~mpi', when='~mpi +adios2')
    depends_on('adios2@2.5.0: +mpi', when='+mpi +adios2')
    depends_on('nlohmann-json@3.9.1:')
    depends_on('py-pybind11@2.6.2:', when='+python', type='link')
    depends_on('py-numpy@1.15.1:', when='+python', type=['test', 'run'])
    depends_on('py-mpi4py@2.1.0:', when='+python +mpi', type=['test', 'run'])
    depends_on('python@3.6:', when='+python', type=['link', 'test', 'run'])

    conflicts('^hdf5 api=v16', msg='openPMD-api requires HDF5 APIs for 1.8+')

    # Fix breaking HDF5 1.12.0 API when build with legacy api options
    # https://github.com/openPMD/openPMD-api/pull/1012
    patch('hdf5-1.12.0.patch', when='@:0.13.99 +hdf5')

    extends('python', when='+python')

    def cmake_args(self):
        spec = self.spec

        args = [
            self.define_from_variant('BUILD_SHARED_LIBS', 'shared'),
            # variants
            self.define_from_variant('openPMD_USE_MPI', 'mpi'),
            self.define_from_variant('openPMD_USE_HDF5', 'hdf5'),
            self.define_from_variant('openPMD_USE_ADIOS1', 'adios1'),
            self.define_from_variant('openPMD_USE_ADIOS2', 'adios2'),
            self.define_from_variant('openPMD_USE_PYTHON', 'python'),
            # tests and examples
            self.define('BUILD_TESTING', self.run_tests),
            self.define('BUILD_EXAMPLES', self.run_tests)
        ]

        # switch internally shipped third-party libraries for spack
        if spec.satisfies('+python'):
            py_exe_define = 'Python_EXECUTABLE' \
                if spec.version >= Version('0.13.0') else 'PYTHON_EXECUTABLE'
            args += [
                self.define(py_exe_define, self.spec['python'].command.path),
                self.define('openPMD_USE_INTERNAL_PYBIND11', False)
            ]

        args += [
            self.define('openPMD_USE_INTERNAL_JSON', False),
            self.define('openPMD_USE_INTERNAL_VARIANT', False)
        ]
        if self.run_tests:
            args.append(self.define('openPMD_USE_INTERNAL_CATCH', False))

        return args

    def setup_run_environment(self, env):
        spec = self.spec
        # pre-load dependent CMake-PUBLIC header-only libs
        env.prepend_path('CMAKE_PREFIX_PATH', spec['mpark-variant'].prefix)
        env.prepend_path('CPATH', spec['mpark-variant'].prefix.include)

        # more deps searched in openPMDConfig.cmake
        if spec.satisfies("+mpi"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['mpi'].prefix)
        if spec.satisfies("+adios1"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['adios'].prefix)
            env.prepend_path('PATH', spec['adios'].prefix.bin)  # adios-config
        if spec.satisfies("+adios2"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['adios2'].prefix)
        if spec.satisfies("+hdf5"):
            env.prepend_path('CMAKE_PREFIX_PATH', spec['hdf5'].prefix)

    def setup_dependent_build_environment(self, env, dependent_spec):
        # pre-load dependent CMake-PUBLIC header-only libs
        env.prepend_path('CMAKE_PREFIX_PATH',
                         self.spec['mpark-variant'].prefix)
        env.prepend_path('CPATH', self.spec['mpark-variant'].prefix.include)
<|endoftext|>


<commit_before># Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from spack import *

class PyLlvmlite(PythonPackage):
    """A lightweight LLVM python binding for writing JIT compilers"""

    homepage = "http://llvmlite.readthedocs.io/en/latest/index.html"
    pypi = "llvmlite/llvmlite-0.23.0.tar.gz"

    version('0.34.0', sha256='f03ee0d19bca8f2fe922bb424a909d05c28411983b0c2bc58b020032a0d11f63')
    version('0.33.0', sha256='9c8aae96f7fba10d9ac864b443d1e8c7ee4765c31569a2b201b3d0b67d8fc596')
    version('0.31.0', sha256='22ab2b9d7ec79fab66ac8b3d2133347de86addc2e2df1b3793e523ac84baa3c8')
    version('0.29.0', sha256='3adb0d4c9a17ad3dca82c7e88118babd61eeee0ee985ce31fa43ec27aa98c963')
    version('0.27.1', sha256='48a1c3ae69fd8920cba153bfed8a46ac46474bc706a2100226df4abffe0000ab')
    version('0.26.0', sha256='13e84fe6ebb0667233074b429fd44955f309dead3161ec89d9169145dbad2ebf')
    version('0.25.0', sha256='fd64def9a51dd7dc61913a7a08eeba5b9785522740bec5a7c5995b2a90525025')
    version('0.23.0', sha256='bc8b1b46274d05b578fe9e980a6d98fa71c8727f6f9ed31d4d8468dce7aa5762')
    version('0.20.0', sha256='b2f174848df16bb9195a07fec102110a06d018da736bd9b3570a54d44c797c29')

    depends_on('py-setuptools', type='build')
    depends_on('python@2.6:2.8,3.4:', type=('build', 'run'))
    depends_on('python@3.6:', type=('build', 'run'), when='@0.33:')
    depends_on('py-enum34', type=('build', 'run'), when='^python@:3.3.99')

    # llvmlite compatibility information taken from https://github.com/numba/llvmlite#compatibility
    depends_on('llvm@10.0:10.0.99', when='@0.34.0:')
    depends_on('llvm@9.0:9.0.99', when='@0.33.0:0.33.99')
    depends_on('llvm@7.0:8.0.99', when='@0.29.0:0.32.99')
    depends_on('llvm@7.0:7.0.99', when='@0.27.0:0.28.99')
    depends_on('llvm@6.0:6.0.99', when='@0.23.0:0.26.99')
    depends_on('llvm@4.0:4.0.99', when='@0.17.0:0.20.99')
    depends_on('binutils', type='build')

    def setup_build_environment(self, env):
        if self.spec.satisfies('%fj'):
            env.set('CXX_FLTO_FLAGS', '{0}'.format(self.compiler.cxx_pic_flag))
            env.set('LD_FLTO_FLAGS', '-Wl,--exclude-libs=ALL')
        else:
            # Need to set PIC flag since this is linking statically with LLVM
            env.set('CXX_FLTO_FLAGS', '-flto {0}'.format(
                self.compiler.cxx_pic_flag))
<commit_msg>py-llvmlite: Specified llvm version for aarch64. (#21695)<commit_after># Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from spack import *

class PyLlvmlite(PythonPackage):
    """A lightweight LLVM python binding for writing JIT compilers"""

    homepage = "http://llvmlite.readthedocs.io/en/latest/index.html"
    pypi = "llvmlite/llvmlite-0.23.0.tar.gz"

    version('0.34.0', sha256='f03ee0d19bca8f2fe922bb424a909d05c28411983b0c2bc58b020032a0d11f63')
    version('0.33.0', sha256='9c8aae96f7fba10d9ac864b443d1e8c7ee4765c31569a2b201b3d0b67d8fc596')
    version('0.31.0', sha256='22ab2b9d7ec79fab66ac8b3d2133347de86addc2e2df1b3793e523ac84baa3c8')
    version('0.29.0', sha256='3adb0d4c9a17ad3dca82c7e88118babd61eeee0ee985ce31fa43ec27aa98c963')
    version('0.27.1', sha256='48a1c3ae69fd8920cba153bfed8a46ac46474bc706a2100226df4abffe0000ab')
    version('0.26.0', sha256='13e84fe6ebb0667233074b429fd44955f309dead3161ec89d9169145dbad2ebf')
    version('0.25.0', sha256='fd64def9a51dd7dc61913a7a08eeba5b9785522740bec5a7c5995b2a90525025')
    version('0.23.0', sha256='bc8b1b46274d05b578fe9e980a6d98fa71c8727f6f9ed31d4d8468dce7aa5762')
    version('0.20.0', sha256='b2f174848df16bb9195a07fec102110a06d018da736bd9b3570a54d44c797c29')

    depends_on('py-setuptools', type='build')
    depends_on('python@2.6:2.8,3.4:', type=('build', 'run'))
    depends_on('python@3.6:', type=('build', 'run'), when='@0.33:')
    depends_on('py-enum34', type=('build', 'run'), when='^python@:3.3.99')

    # llvmlite compatibility information taken from https://github.com/numba/llvmlite#compatibility
    for t in ['arm:', 'ppc:', 'ppc64:', 'ppc64le:', 'ppcle:',
              'sparc:', 'sparc64:', 'x86:', 'x86_64:']:
        depends_on('llvm@10.0:10.0.99~flang', when='@0.34.0: target={0}'.format(t))
    depends_on('llvm@9.0:9.0.99~flang', when='@0.34.0: target=aarch64:')
    depends_on('llvm@9.0:9.0.99~flang', when='@0.33.0:0.33.99')
    depends_on('llvm@7.0:8.0.99~flang', when='@0.29.0:0.32.99')
    depends_on('llvm@7.0:7.0.99~flang', when='@0.27.0:0.28.99')
    depends_on('llvm@6.0:6.0.99~flang', when='@0.23.0:0.26.99')
    depends_on('llvm@4.0:4.0.99~flang', when='@0.17.0:0.20.99')
    depends_on('binutils', type='build')

    def setup_build_environment(self, env):
        if self.spec.satisfies('%fj'):
            env.set('CXX_FLTO_FLAGS', '{0}'.format(self.compiler.cxx_pic_flag))
            env.set('LD_FLTO_FLAGS', '-Wl,--exclude-libs=ALL')
        else:
            # Need to set PIC flag since this is linking statically with LLVM
            env.set('CXX_FLTO_FLAGS', '-flto {0}'.format(
                self.compiler.cxx_pic_flag))
<|endoftext|>


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

# References
# http://docs.python.org/3.3/library/unittest.html

import unittest
import copyspecial
import os

class TestCopySpecial(unittest.TestCase):

    def setUp(self):
        pass

    def get_paths(self, a_dir):
        """
        return a list of the absolute paths of the files in the given directory
        """

        filenames = os.listdir(a_dir)
        file_list = []
        for filename in filenames:
            a_path = os.path.join(a_dir, filename)
            absolute_path = os.path.abspath(a_path)
            file_list.append(absolute_path)
        return file_list

    def test_get_special_paths(self):
        test_dir = '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial'
        result = copyspecial.get_special_paths(test_dir)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg'
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               len(expected_result),
                                                                               len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               expected_result,
                                                                               result))

        test_dir = '.'
        result = copyspecial.get_special_paths(test_dir)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg'
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               len(expected_result),
                                                                               len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               expected_result,
                                                                               result))

    def test_get_special_paths_in_dirs(self):
        """
        TODO: Test 2 directories with repeated files
        """
        test_dirs = ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial',
                     '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir']
        result = copyspecial.get_special_paths_in_dirs(test_dirs)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir/anotherxyz__hello__.txt',
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths_in_dirs({}) expected {} but got {}'.format(test_dirs,
                                                                                len(expected_result),
                                                                                len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths_in_dirs({}) expected {} but got {}'.format(test_dirs,
                                                                                expected_result,
                                                                                result))

    def test_is_special_path(self):

        test_path_index = 0
        expected_result_index = 1

        test_datas = [
            ['', False],
            ['abc', False],
            ['__bar', False],
            ['____', False],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/copyspecial.py', False],
            ['__foo__', True],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt', True],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/__pycache__', True],
        ]

        for test_data in test_datas:
            test_path = test_data[test_path_index]
            result = copyspecial.is_special_path(test_path)
            expected_result = test_data[expected_result_index]
            self.assertEqual(expected_result, result,
                             'is_special_path({}) expected {} but got {}'.format(test_path,
                                                                                expected_result,
                                                                                result))

    def test_copy_to(self):
        paths = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/__pycache__',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg'
        ]
        test_to_dir = '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir'

        copyspecial.copy_to(paths, test_to_dir)
        result = self.get_paths(test_to_dir)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/__pycache__',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir/anotherxyz__hello__.txt',
        ]

        self.assertEqual(len(expected_result), len(result),
                         'expected {} but got {}'.format(
                             len(expected_result),
                             len(result)))
        self.assertEqual(expected_result, result,
                         'expected {} but got {}'.format(
                             expected_result,
                             result))

        #TODO: delete directory contents for next test

if __name__ == "__main__": unittest.main()
<commit_msg>In test_copyspecial.py test_copy_to, don't attempt to copy directory __pycache__. Fix expected_result.<commit_after>#!/usr/bin/env python3

# References
# http://docs.python.org/3.3/library/unittest.html

import unittest
import copyspecial
import os

class TestCopySpecial(unittest.TestCase):

    def setUp(self):
        pass

    def get_paths(self, a_dir):
        """
        return a list of the absolute paths of the files in the given directory
        """

        filenames = os.listdir(a_dir)
        file_list = []
        for filename in filenames:
            a_path = os.path.join(a_dir, filename)
            absolute_path = os.path.abspath(a_path)
            file_list.append(absolute_path)
        return file_list

    def test_get_special_paths(self):
        test_dir = '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial'
        result = copyspecial.get_special_paths(test_dir)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg'
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               len(expected_result),
                                                                               len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               expected_result,
                                                                               result))

        test_dir = '.'
        result = copyspecial.get_special_paths(test_dir)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg'
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               len(expected_result),
                                                                               len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths({}) expected {} but got {}'.format(test_dir,
                                                                               expected_result,
                                                                               result))

    def test_get_special_paths_in_dirs(self):
        """
        TODO: Test 2 directories with repeated files
        """
        test_dirs = ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial',
                     '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir']
        result = copyspecial.get_special_paths_in_dirs(test_dirs)

        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir/anotherxyz__hello__.txt',
        ]

        self.assertEqual(len(expected_result), len(result),
                         'get_special_paths_in_dirs({}) expected {} but got {}'.format(test_dirs,
                                                                                len(expected_result),
                                                                                len(result)))
        self.assertEqual(expected_result, result,
                         'get_special_paths_in_dirs({}) expected {} but got {}'.format(test_dirs,
                                                                                expected_result,
                                                                                result))

    def test_is_special_path(self):

        test_path_index = 0
        expected_result_index = 1

        test_datas = [
            ['', False],
            ['abc', False],
            ['__bar', False],
            ['____', False],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/copyspecial.py', False],
            ['__foo__', True],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt', True],
            ['/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/__pycache__', True],
        ]

        for test_data in test_datas:
            test_path = test_data[test_path_index]
            result = copyspecial.is_special_path(test_path)
            expected_result = test_data[expected_result_index]
            self.assertEqual(expected_result, result,
                             'is_special_path({}) expected {} but got {}'.format(test_path,
                                                                                expected_result,
                                                                                result))

    #@unittest.skip("skip this test")
    def test_copy_to(self):
        paths = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/zz__something__.jpg',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_from_dir/anotherxyz__hello__.txt',
        ]
        test_to_dir = '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir'

        copyspecial.copy_to(paths, test_to_dir)
        result = self.get_paths(test_to_dir)

        # expected_result list is sorted alphabetically
        # OS X adds .DS_Store, so expect it
        expected_result = [
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir/.DS_Store',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir/anotherxyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir/xyz__hello__.txt',
            '/Users/stevebaker/Documents/projects/pythonProjects/google-python-exercises/copyspecial/test_to_dir/zz__something__.jpg',
        ]

        self.assertEqual(len(expected_result), len(result),
                         'expected {} but got {}'.format(
                             len(expected_result),
                             len(result)))
        self.assertEqual(expected_result, result,
                         'expected {} but got {}'.format(
                             expected_result,
                             result))

        #TODO: delete directory contents for next test

if __name__ == "__main__": unittest.main()
<|endoftext|>


<commit_before>from __future__ import absolute_import

import logging
from typing import Any, Dict, List, Optional, Text

from argparse import ArgumentParser
from zerver.models import UserProfile
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

from django.contrib.auth.tokens import default_token_generator, PasswordResetTokenGenerator

from zerver.lib.send_email import send_email, FromAddress
from zerver.lib.management import ZulipBaseCommand

class Command(ZulipBaseCommand):
    help = """Send email to specified email address."""

    def add_arguments(self, parser):
        # type: (ArgumentParser) -> None
        parser.add_argument('--to', metavar='<to>', type=str,
                            help="email of users to send the email")
        parser.add_argument('--target', metavar='<target>', type=str,
                            help="If you pass 'server' will send to everyone on server. "
                                 "If you pass 'realm' will send to everyone on realm."
                                 "Don't forget to specify the realm using -r or --realm flag.")
        self.add_realm_args(parser)

    def handle(self, *args, **options):
        # type: (*Any, **str) -> None
        realm = self.get_realm(options)
        if options["to"] and options["target"]:
            self.print_help("./manage.py", "send_password_reset_email")
            exit(1)
        if options["to"]:
            users = [self.get_user(options["to"], realm)]
        elif options["target"] == "realm":
            users = UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False,
                                               is_mirror_dummy=False)
        elif options["target"] == "server":
            users = UserProfile.objects.filter(is_active=True, is_bot=False,
                                               is_mirror_dummy=False)
        self.send(users)

    def send(self, users, subject_template_name='', email_template_name='',
             use_https=True, token_generator=default_token_generator,
             from_email=None, html_email_template_name=None):
        # type: (List[UserProfile], str, str, bool, PasswordResetTokenGenerator, Optional[Text], Optional[str]) -> None
        """Sends one-use only links for resetting password to target users

        """
        for user_profile in users:
            context = {
                'email': user_profile.email,
                'domain': user_profile.realm.host,
                'site_name': "zulipo",
                'uid': urlsafe_base64_encode(force_bytes(user_profile.pk)),
                'user': user_profile,
                'token': token_generator.make_token(user_profile),
                'protocol': 'https' if use_https else 'http',
            }

            logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,))
            send_email('zerver/emails/password_reset', to_user_id=user_profile.id,
                       from_name="Zulip Account Security", from_address=FromAddress.NOREPLY,
                       context=context)
<commit_msg>management: Use add_user_list_args in send_password_reset_email.<commit_after>from __future__ import absolute_import

import logging
from typing import Any, Dict, List, Optional, Text

from argparse import ArgumentParser
from zerver.models import UserProfile
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

from django.contrib.auth.tokens import default_token_generator, PasswordResetTokenGenerator

from zerver.lib.send_email import send_email, FromAddress
from zerver.lib.management import ZulipBaseCommand

class Command(ZulipBaseCommand):
    help = """Send email to specified email address."""

    def add_arguments(self, parser):
        # type: (ArgumentParser) -> None
        parser.add_argument('--target', metavar='<target>', type=str,
                            help="If you pass 'server' will send to everyone on server. "
                                 "If you pass 'realm' will send to everyone on realm."
                                 "Don't forget to specify the realm using -r or --realm flag.")
        self.add_user_list_args(parser,
                                help="Email addresses of user(s) to send password reset emails to.")
        self.add_realm_args(parser)

    def handle(self, *args, **options):
        # type: (*Any, **str) -> None
        realm = self.get_realm(options)
        users = self.get_users(options, realm)

        if bool(users) == bool(options["target"]):
            self.print_help("./manage.py", "send_password_reset_email")
            exit(1)

        if options["target"] == "realm":
            users = UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False,
                                               is_mirror_dummy=False)
        elif options["target"] == "server":
            users = UserProfile.objects.filter(is_active=True, is_bot=False,
                                               is_mirror_dummy=False)
        self.send(users)

    def send(self, users, subject_template_name='', email_template_name='',
             use_https=True, token_generator=default_token_generator,
             from_email=None, html_email_template_name=None):
        # type: (List[UserProfile], str, str, bool, PasswordResetTokenGenerator, Optional[Text], Optional[str]) -> None
        """Sends one-use only links for resetting password to target users

        """
        for user_profile in users:
            context = {
                'email': user_profile.email,
                'domain': user_profile.realm.host,
                'site_name': "zulipo",
                'uid': urlsafe_base64_encode(force_bytes(user_profile.pk)),
                'user': user_profile,
                'token': token_generator.make_token(user_profile),
                'protocol': 'https' if use_https else 'http',
            }

            logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,))
            send_email('zerver/emails/password_reset', to_user_id=user_profile.id,
                       from_name="Zulip Account Security", from_address=FromAddress.NOREPLY,
                       context=context)
<|endoftext|>


<commit_before>import time
import warnings

try:
    # Python 3
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

from .config import config
from .providers import Providers
from .tools import process, thread, noxml

class HTTPClient(object):
    """ Emulate the behaviour of a RTMP client when there's an HTTP access
        for a certain Stream. If no other HTTP access is made within the
        timeout period, the `Stream` instance will be decremented.
    """
    def __init__(self, parent):
        self.lock = thread.Condition()
        self.timeout = None
        self.parent = parent
        self._stop()

    def wait(self, timeout):
        self.timeout = timeout
        if not self._stopped:
            with self.lock:
                self._stop(restart=True)
                self.lock.notify_all()
        else:
            self.thread = thread.Thread(self._wait_worker).start()
        return self

    def _wait_worker(self):
        with self.lock:
            while self._stopped:
                self._start()
                self.lock.wait(self.timeout)
            self._stop()
            self.parent.dec(http=True)

    def _stop(self, restart=False, data=True):
        self._stopped = data
        if not restart:
            self._stopped_info = data

    def _start(self):
        self._stop(data=False)

    def __bool__(self):
        return not self._stopped_info
    __nonzero__ = __bool__

class Stream(object):
    _ffmpeg = config['ffmpeg']
    run_timeout = _ffmpeg.getint('timeout')
    reload_timeout = _ffmpeg.getint('reload')

    def __init__(self, id, timeout=run_timeout):
        self.lock = thread.Lock()
        self.id = id
        provider = Providers.select(id)
        self.fn = lambda self=self: process.run_proc(
            self.id,
            provider.make_cmd(self.id),
            'fetch'
        )
        self.cnt = 0
        self._proc_run = False
        self.proc = None
        self.thread = None
        self.timeout = timeout
        self.http_client = HTTPClient(self)

    def __repr__(self):
        pid = self.proc.pid if self.proc else None
        return '<{0}: Users={1} Pid={2}>'.format(self.id, self.clients, pid)

    @property
    def clients(self):
        return self.cnt + bool(self.http_client)

    @property
    def alive(self):
        return self.proc or self.proc_run

    @property
    def proc_run(self):
        return self._proc_run

    @proc_run.setter
    def proc_run(self, value):
        with self.lock:
            self._proc_run = value

    def inc(self, k=1, http_wait=None):
        """ Increment user count unless it is a http user (then http_wait
            must be set). If so, it should wait a period of time on another
            thread and the clients property will be indirectly incremented.

            If there is no process running and it should be, a new process
            will be started.
        """
        if http_wait:
            self.http_client.wait(http_wait)
        else:
            self.cnt += k
        if not self.proc and not self.proc_run:
            self.proc_start()
        print(self)
        return self

    def dec(self, http=False):
        """ Decrement the user count unless it is a http user. If there are no
            new clients, the process is scheduled to shutdown.
        """
        if not http:
            if self.cnt:
                self.cnt -= 1
        if not self.clients:
            self.proc_stop()
        print(self)
        return self

    def _proc_msg(self, pid, msg):
        return '{0} - FFmpeg[{1}] {2}'.format(self.id, pid, msg)

    def proc_start(self):
        """ Process starter on another thread.
        """
        def worker():
            self.proc_run = True
            start_msg = 'started'
            while True:
                with self.fn() as self.proc:
                    pid = self.proc and self.proc.pid
                    print(self._proc_msg(pid, start_msg))
                    self.proc.wait()
                    self.proc = None

                    if self.proc_run:  # Should be running, but isn't
                        print(self._proc_msg(pid, 'died'))
                        time.sleep(self.reload_timeout)
                        if self.proc_run:  # It might have been stopped after waiting
                            start_msg = 'restarted'
                            continue
                    print(self._proc_msg(pid, 'stopped'))
                    break

        self.thread = thread.Thread(worker).start()

    def _kill(self):
        """ Kill the FFmpeg process. Don't call this function directly,
            otherwise the process may be restarted. Call `proc_stop` instead.
        """
        try:
            self.proc.kill()
            self.proc.wait()
        except (OSError, AttributeError):
            pass
        finally:
            self.proc = None

    def proc_stop(self, now=False):
        if now:
            self.proc_run = False
            self._kill()
            return

        if not self.proc_run:
            return
        self.proc_run = False

        def stop_worker():
            time.sleep(self.timeout)
            if not self.clients:
                self._kill()
            else:
                self.proc_run = True

        thread.Thread(stop_worker).start()

class Video(object):
    _data = {}
    _data_lock = thread.Lock()
    run = True

    @classmethod
    def start(cls, id, increment=1, http_wait=None):
        if cls.run:
            cls.get_stream(id).inc(increment, http_wait=http_wait)

    @classmethod
    def stop(cls, id):
        cls.get_stream(id).dec()

    @classmethod
    def get_stream(cls, id):
        with cls._data_lock:
            stream = cls._data.get(id)
            if stream is None:
                stream = Stream(id)
                cls._data[id] = stream
            return stream

    @classmethod
    def get_stats(cls):
        http = config['http-server']
        addr = http['addr']
        stat = http['stat_url']
        data = urlopen(addr + stat).read()
        return noxml.load(data, ('stream', 'application'))

    @classmethod
    def initialize_from_stats(cls):
        try:
            stats = cls.get_stats()['server']['application']
        except IOError:
            return

        app = config['rtmp-server']['app']
        try:
            app = next(x['live'] for x in stats if x['name'] == app)
        except StopIteration:
            raise RuntimeError('No app named %r' % app)

        # App clients
        stream_list = app.get('stream')
        if stream_list is None:
            return

        for stream in stream_list:
            # Stream clients
            nclients = int(stream['nclients'])

            if 'publishing' in stream:
                nclients -= 1

            if nclients <= 0:
                continue

            try:
                cls.start(stream['name'], nclients)
            except KeyError:
                warnings.warn('Invalid stream name: %r' % stream['name'])

    @classmethod
    def auto_start(cls):
        for id in config['general'].get_list('auto_start'):
            cls.start(id)

    @classmethod
    def terminate_streams(cls):
        with cls._data_lock:
            cls.run = False
            for strm in cls._data.values():
                strm.proc_stop(now=True)
<commit_msg>HTTPClient renomeado para StreamHTTPClient<commit_after>import time
import warnings

try:
    # Python 3
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

from .config import config
from .providers import Providers
from .tools import process, thread, noxml

class StreamHTTPClient(object):
    """ Emulate the behaviour of a RTMP client when there's an HTTP access
        for a certain Stream. If no other HTTP access is made within the
        timeout period, the `Stream` instance will be decremented.
    """
    def __init__(self, parent):
        self.lock = thread.Condition()
        self.timeout = None
        self.parent = parent
        self._stop()

    def wait(self, timeout):
        self.timeout = timeout
        if not self._stopped:
            with self.lock:
                self._stop(restart=True)
                self.lock.notify_all()
        else:
            self.thread = thread.Thread(self._wait_worker).start()
        return self

    def _wait_worker(self):
        with self.lock:
            while self._stopped:
                self._start()
                self.lock.wait(self.timeout)
            self._stop()
            self.parent.dec(http=True)

    def _stop(self, restart=False, data=True):
        self._stopped = data
        if not restart:
            self._stopped_info = data

    def _start(self):
        self._stop(data=False)

    def __bool__(self):
        return not self._stopped_info
    __nonzero__ = __bool__

class Stream(object):
    _ffmpeg = config['ffmpeg']
    run_timeout = _ffmpeg.getint('timeout')
    reload_timeout = _ffmpeg.getint('reload')

    def __init__(self, id, timeout=run_timeout):
        self.lock = thread.Lock()
        self.id = id
        provider = Providers.select(id)
        self.fn = lambda self=self: process.run_proc(
            self.id,
            provider.make_cmd(self.id),
            'fetch'
        )
        self.cnt = 0
        self._proc_run = False
        self.proc = None
        self.thread = None
        self.timeout = timeout
        self.http_client = StreamHTTPClient(self)

    def __repr__(self):
        pid = self.proc.pid if self.proc else None
        return '<{0}: Users={1} Pid={2}>'.format(self.id, self.clients, pid)

    @property
    def clients(self):
        return self.cnt + bool(self.http_client)

    @property
    def alive(self):
        return self.proc or self.proc_run

    @property
    def proc_run(self):
        return self._proc_run

    @proc_run.setter
    def proc_run(self, value):
        with self.lock:
            self._proc_run = value

    def inc(self, k=1, http_wait=None):
        """ Increment user count unless it is a http user (then http_wait
            must be set). If so, it should wait a period of time on another
            thread and the clients property will be indirectly incremented.

            If there is no process running and it should be, a new process
            will be started.
        """
        if http_wait:
            self.http_client.wait(http_wait)
        else:
            self.cnt += k
        if not self.proc and not self.proc_run:
            self.proc_start()
        print(self)
        return self

    def dec(self, http=False):
        """ Decrement the user count unless it is a http user. If there are no
            new clients, the process is scheduled to shutdown.
        """
        if not http:
            if self.cnt:
                self.cnt -= 1
        if not self.clients:
            self.proc_stop()
        print(self)
        return self

    def _proc_msg(self, pid, msg):
        return '{0} - FFmpeg[{1}] {2}'.format(self.id, pid, msg)

    def proc_start(self):
        """ Process starter on another thread.
        """
        def worker():
            self.proc_run = True
            start_msg = 'started'
            while True:
                with self.fn() as self.proc:
                    pid = self.proc and self.proc.pid
                    print(self._proc_msg(pid, start_msg))
                    self.proc.wait()
                    self.proc = None

                    if self.proc_run:  # Should be running, but isn't
                        print(self._proc_msg(pid, 'died'))
                        time.sleep(self.reload_timeout)
                        if self.proc_run:  # It might have been stopped after waiting
                            start_msg = 'restarted'
                            continue
                    print(self._proc_msg(pid, 'stopped'))
                    break

        self.thread = thread.Thread(worker).start()

    def _kill(self):
        """ Kill the FFmpeg process. Don't call this function directly,
            otherwise the process may be restarted. Call `proc_stop` instead.
        """
        try:
            self.proc.kill()
            self.proc.wait()
        except (OSError, AttributeError):
            pass
        finally:
            self.proc = None

    def proc_stop(self, now=False):
        if now:
            self.proc_run = False
            self._kill()
            return

        if not self.proc_run:
            return
        self.proc_run = False

        def stop_worker():
            time.sleep(self.timeout)
            if not self.clients:
                self._kill()
            else:
                self.proc_run = True

        thread.Thread(stop_worker).start()

class Video(object):
    _data = {}
    _data_lock = thread.Lock()
    run = True

    @classmethod
    def start(cls, id, increment=1, http_wait=None):
        if cls.run:
            cls.get_stream(id).inc(increment, http_wait=http_wait)

    @classmethod
    def stop(cls, id):
        cls.get_stream(id).dec()

    @classmethod
    def get_stream(cls, id):
        with cls._data_lock:
            stream = cls._data.get(id)
            if stream is None:
                stream = Stream(id)
                cls._data[id] = stream
            return stream

    @classmethod
    def get_stats(cls):
        http = config['http-server']
        addr = http['addr']
        stat = http['stat_url']
        data = urlopen(addr + stat).read()
        return noxml.load(data, ('stream', 'application'))

    @classmethod
    def initialize_from_stats(cls):
        try:
            stats = cls.get_stats()['server']['application']
        except IOError:
            return

        app = config['rtmp-server']['app']
        try:
            app = next(x['live'] for x in stats if x['name'] == app)
        except StopIteration:
            raise RuntimeError('No app named %r' % app)

        # App clients
        stream_list = app.get('stream')
        if stream_list is None:
            return

        for stream in stream_list:
            # Stream clients
            nclients = int(stream['nclients'])

            if 'publishing' in stream:
                nclients -= 1

            if nclients <= 0:
                continue

            try:
                cls.start(stream['name'], nclients)
            except KeyError:
                warnings.warn('Invalid stream name: %r' % stream['name'])

    @classmethod
    def auto_start(cls):
        for id in config['general'].get_list('auto_start'):
            cls.start(id)

    @classmethod
    def terminate_streams(cls):
        with cls._data_lock:
            cls.run = False
            for strm in cls._data.values():
                strm.proc_stop(now=True)
<|endoftext|>


<commit_before># Copyright 2021 The Duet Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Internal implementation details for duet."""

import enum
import functools
import heapq
import itertools
import signal
import threading
import time
from concurrent.futures import Future
from contextvars import ContextVar
from typing import (
    Any,
    Awaitable,
    Callable,
    cast,
    Coroutine,
    Generic,
    Iterator,
    List,
    Optional,
    Set,
    TypeVar,
)

import duet.futuretools as futuretools

T = TypeVar("T")

class Interrupt(BaseException):
    def __init__(self, task, error):
        self.task = task
        self.error = error

class TaskState(enum.Enum):
    WAITING = 0
    SUCCEEDED = 1
    FAILED = 2

class TaskStateError(Exception):
    def __init__(self, state: TaskState, expected_state: TaskState) -> None:
        self.state = state
        self.expected_state = expected_state
        super().__init__(f"state: {state}, expected: {expected_state}")

# Sentinel local variable name that we insert into coroutines.
# This allows us to detect whether a task is running when we get Ctrl-C.
LOCALS_TASK_SCHEDULER = "__duet_task_scheduler__"

class Task(Generic[T]):
    def __init__(
        self,
        awaitable: Awaitable[T],
        scheduler: "Scheduler",
        main_task: Optional["Task"],
    ) -> None:
        self.scheduler = scheduler
        self.main_task = main_task
        self._state = TaskState.WAITING
        self._future: Optional[Future] = None
        self._ready_future = futuretools.AwaitableFuture[None]()
        self._ready_future.set_result(None)  # Ready to advance.
        self.interruptible = True
        self._interrupt: Optional[Interrupt] = None
        self._result: Optional[T] = None
        self._error: Optional[Exception] = None
        self._deadlines: List[DeadlineEntry] = []
        if main_task and main_task.deadline is not None:
            self.push_deadline(main_task.deadline)
        self._generator = awaitable.__await__()  # Returns coroutine generator.
        if isinstance(awaitable, Coroutine):
            awaitable.cr_frame.f_locals.setdefault(LOCALS_TASK_SCHEDULER, scheduler)

    def _check_state(self, expected_state: TaskState) -> None:
        if self._state != expected_state:
            raise TaskStateError(self._state, expected_state)

    @property
    def future(self) -> Optional[Future]:
        self._check_state(TaskState.WAITING)
        return self._future

    @property
    def result(self) -> T:
        self._check_state(TaskState.SUCCEEDED)
        return cast(T, self._result)

    @property
    def done(self) -> bool:
        return self._state == TaskState.SUCCEEDED or self._state == TaskState.FAILED

    def add_ready_callback(self, callback: Callable[["Task"], Any]) -> None:
        self._check_state(TaskState.WAITING)
        self._ready_future.add_done_callback(lambda _: callback(self))

    def advance(self):
        if self.done:
            return
        if self._state == TaskState.WAITING:
            self._ready_future.result()
        token = _current_task.set(self)
        try:
            if self._interrupt:
                interrupt = self._interrupt
                self._interrupt = None
                if interrupt.task is self:
                    error = interrupt.error
                else:
                    error = interrupt
                f = self._generator.throw(error)
            else:
                f = next(self._generator)
        except StopIteration as e:
            self._result = e.value
            self._state = TaskState.SUCCEEDED
            return
        except (Interrupt, Exception) as error:
            self._error = error
            self._state = TaskState.FAILED
            if self.main_task:
                self.main_task.interrupt(self, error)
                return
            else:
                raise
        else:
            if not isinstance(f, Future):
                raise TypeError(f"expected Future, got {type(f)}: {f}")
            ready_future = futuretools.AwaitableFuture()
            f.add_done_callback(lambda _: ready_future.try_set_result(None))
            self._future = f
            self._ready_future = ready_future
            self._state = TaskState.WAITING
        finally:
            _current_task.reset(token)

    def push_deadline(self, deadline: float) -> None:
        if self._deadlines:
            deadline = min(self._deadlines[-1].deadline, deadline)
        entry = self.scheduler.add_deadline(self, deadline)
        self._deadlines.append(entry)

    def pop_deadline(self) -> None:
        entry = self._deadlines.pop(-1)
        entry.valid = False

    @property
    def deadline(self) -> Optional[float]:
        return self._deadlines[-1].deadline if self._deadlines else None

    def interrupt(self, task, error):
        if self.done or not self.interruptible or self._interrupt:
            return
        self._interrupt = Interrupt(task, error)
        self._ready_future.try_set_result(None)
        if self._future:
            self._future.cancel()

    def close(self):
        self._generator.close()
        self.scheduler = None
        self.main_task = None

_current_task: ContextVar[Task] = ContextVar("current_task")

def current_task() -> Task:
    """Gets the currently-running duet task.

    This must be called from within a running async function, or else it will
    raise a RuntimeError.
    """
    try:
        return _current_task.get()
    except LookupError:
        raise RuntimeError("Can only be called from an async function.")

def current_scheduler() -> "Scheduler":
    """Gets the currently-running duet scheduler.

    This must be called from within a running async function, or else it will
    raise a RuntimeError.
    """
    return current_task().scheduler

def any_ready(tasks: Set[Task]) -> futuretools.AwaitableFuture[None]:
    """Returns a Future that will fire when any of the given tasks is ready."""
    if not tasks or any(task.done for task in tasks):
        return futuretools.completed_future(None)
    f = futuretools.AwaitableFuture[None]()
    for task in tasks:
        task.add_ready_callback(lambda _: f.try_set_result(None))
    return f

class ReadySet:
    """Container for an ordered set of tasks that are ready to advance."""

    def __init__(self):
        self._cond = threading.Condition()
        self._buffer = futuretools.BufferGroup()
        self._tasks: List[Task] = []
        self._task_set: Set[Task] = set()

    def register(self, task: Task) -> None:
        """Registers task to be added to this set when it is ready."""
        self._buffer.add(task.future)
        task.add_ready_callback(self._add)

    def _add(self, task: Task) -> None:
        """Adds the given task to the ready set, if it is not already there."""
        with self._cond:
            if task not in self._task_set:
                self._task_set.add(task)
                self._tasks.append(task)
                self._cond.notify()

    def get_all(self, timeout: Optional[float] = None) -> List[Task]:
        """Gets all ready tasks and clears the ready set.

        If no tasks are ready yet, we flush buffered futures to notify them
        that they should proceed, and then block until one or more tasks become
        ready.
        """
        with self._cond:
            if self._tasks:
                return self._pop_tasks()
        # Flush buffered futures to ensure we make progress. Note that we must
        # release the condition lock before flushing to avoid a deadlock if
        # buffered futures complete and trigger a call to self._add.
        self._buffer.flush()
        with self._cond:
            if not self._tasks:
                if not self._cond.wait(timeout):
                    raise TimeoutError()
            return self._pop_tasks()

    def _pop_tasks(self) -> List[Task]:
        tasks = self._tasks
        self._tasks = []
        self._task_set.clear()
        return tasks

    def interrupt(self) -> None:
        with self._cond:
            self._cond.notify()

@functools.total_ordering
class DeadlineEntry:
    """A entry for one Deadline in the Scheduler's priority queue.

    This follows the implementation notes in the stdlib heapq docs:
    https://docs.python.org/3/library/heapq.html#priority-queue-implementation-notes

    Attributes:
        task: The task associated with this deadline.
        deadline: Absolute time when the deadline will elapse.
        count: Monotonically-increasing counter to preserve creation order when
            comparing entries with the same deadline.
        valid: Flag indicating whether the deadline is still valid. If the task
            exits its scope before the deadline elapses, we mark the deadline as
            invalid but leave it in the scheduler's priority queue since removal
            would require an O(n) scan. The scheduler ignores invalid deadlines
            when they elapse.
    """

    _counter = itertools.count()

    def __init__(self, task: Task, deadline: float):
        self.task = task
        self.deadline = deadline
        self.count = next(self._counter)
        self._cmp_val = (deadline, self.count)
        self.valid = True

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, DeadlineEntry):
            return NotImplemented
        return self._cmp_val == other._cmp_val

    def __lt__(self, other: Any) -> bool:
        if not isinstance(other, DeadlineEntry):
            return NotImplemented
        return self._cmp_val < other._cmp_val

    def __repr__(self) -> str:
        return f"DeadlineEntry({self.task}, {self.deadline}, {self.count})"

class Scheduler:
    def __init__(self) -> None:
        self.active_tasks: Set[Task] = set()
        self._ready_tasks = ReadySet()
        self._prev_signal: Optional[Callable] = None
        self._interrupted = False
        self._deadlines: List[DeadlineEntry] = []

    def spawn(self, awaitable: Awaitable[Any], main_task: Optional[Task] = None) -> Task:
        """Spawns a new Task to run an awaitable in this Scheduler.

        Note that the task will not be advanced until the next scheduler tick.
        Also, note that this function is safe to call from sync code (such as
        duet.run) or async code (such as within a scope).

        Args:
            func: The async function to run.
            *args: Args for func.
            **kwds: Keyword args for func.

        Returns:
            A Task to run the given awaitable.
        """
        task = Task(awaitable, scheduler=self, main_task=main_task)
        self.active_tasks.add(task)
        self._ready_tasks.register(task)
        return task

    def time(self) -> float:
        return time.time()

    def add_deadline(self, task: Task, deadline: float) -> DeadlineEntry:
        entry = DeadlineEntry(task, deadline=deadline)
        heapq.heappush(self._deadlines, entry)
        return entry

    def get_next_deadline(self) -> Optional[float]:
        while self._deadlines:
            if not self._deadlines[0].valid:
                heapq.heappop(self._deadlines)
                continue
            return self._deadlines[0].deadline
        return None

    def get_deadline_tasks(self, deadline: float) -> Iterator[Task]:
        while self._deadlines and self._deadlines[0].deadline <= deadline:
            entry = heapq.heappop(self._deadlines)
            if entry.valid:
                yield entry.task

    def tick(self):
        """Runs the scheduler ahead by one tick.

        This waits for at least one active task to complete, then advances all
        ready tasks and sets up a new future to be notified later by tasks that
        are still active (or yet to be spawned). Raises a RuntimeError if there
        are no currently active tasks.
        """
        if not self.active_tasks:
            raise RuntimeError("tick called with no active tasks")

        if self._interrupted:
            task = next(iter(self.active_tasks))
            task.interrupt(task, KeyboardInterrupt)
            self._interrupted = False

        deadline = self.get_next_deadline()
        timeout = None if deadline is None else max(deadline - self.time(), 0)
        try:
            ready_tasks = self._ready_tasks.get_all(timeout)
        except TimeoutError:
            for task in self.get_deadline_tasks(deadline):
                task.interrupt(task, TimeoutError())
            ready_tasks = self._ready_tasks.get_all(None)
        for task in ready_tasks:
            try:
                task.advance()
            finally:
                if task.done:
                    task.close()
                    self.active_tasks.discard(task)
                else:
                    self._ready_tasks.register(task)

    def _interrupt(self, signum: int, frame: Optional[Any]) -> None:
        """Interrupt signal handler used while this scheduler is running.

        This is inspired by trio's interrupt handling, described here:
        https://vorpus.org/blog/control-c-handling-in-python-and-trio/

        If the interrupted frame is inside a running task, which we detect by
        looking for a special local variable inserted into the task coroutine,
        we simply raise a KeyboardInterrupt as usual. Otherwise we set a flag
        which will get checked on the next tick() and cause a task to be
        interrupted.

        One important difference from trio is that duet is reentrant, so when
        detecting whether we are in a task we have to check whether the task's
        scheduler is self. If the interrupted frame is running in a task of a
        different scheduler, that should not raise KeyboardInterrupt directly.
        """
        if self._in_task(frame):
            raise KeyboardInterrupt
        else:
            self._interrupted = True
            self._ready_tasks.interrupt()

    def _in_task(self, frame) -> bool:
        while frame is not None:
            if frame.f_locals.get(LOCALS_TASK_SCHEDULER, None) is self:
                return True
            frame = frame.f_back
        return False

    def __enter__(self):
        if (
            threading.current_thread() == threading.main_thread()
            and signal.getsignal(signal.SIGINT) == signal.default_int_handler
        ):
            self._prev_signal = signal.signal(signal.SIGINT, self._interrupt)
        return self

    def __exit__(self, exc_type, exc, tb):
        def finish_tasks(error=None):
            if error:
                for task in self.active_tasks:
                    task.interrupt(None, error)
            while self.active_tasks:
                try:
                    self.tick()
                except Exception:
                    if not error:
                        raise

        try:
            if exc:
                finish_tasks(exc)
            else:
                try:
                    finish_tasks()
                except Exception as exc:
                    finish_tasks(exc)
                    raise
        finally:
            if self._prev_signal:
                signal.signal(signal.SIGINT, self._prev_signal)
<commit_msg>Handle timeouts larger than threading.TIMEOUT_MAX (#39)<commit_after># Copyright 2021 The Duet Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Internal implementation details for duet."""

import enum
import functools
import heapq
import itertools
import signal
import threading
import time
from concurrent.futures import Future
from contextvars import ContextVar
from typing import (
    Any,
    Awaitable,
    Callable,
    cast,
    Coroutine,
    Generic,
    Iterator,
    List,
    Optional,
    Set,
    TypeVar,
)

import duet.futuretools as futuretools

T = TypeVar("T")

class Interrupt(BaseException):
    def __init__(self, task, error):
        self.task = task
        self.error = error

class TaskState(enum.Enum):
    WAITING = 0
    SUCCEEDED = 1
    FAILED = 2

class TaskStateError(Exception):
    def __init__(self, state: TaskState, expected_state: TaskState) -> None:
        self.state = state
        self.expected_state = expected_state
        super().__init__(f"state: {state}, expected: {expected_state}")

# Sentinel local variable name that we insert into coroutines.
# This allows us to detect whether a task is running when we get Ctrl-C.
LOCALS_TASK_SCHEDULER = "__duet_task_scheduler__"

class Task(Generic[T]):
    def __init__(
        self,
        awaitable: Awaitable[T],
        scheduler: "Scheduler",
        main_task: Optional["Task"],
    ) -> None:
        self.scheduler = scheduler
        self.main_task = main_task
        self._state = TaskState.WAITING
        self._future: Optional[Future] = None
        self._ready_future = futuretools.AwaitableFuture[None]()
        self._ready_future.set_result(None)  # Ready to advance.
        self.interruptible = True
        self._interrupt: Optional[Interrupt] = None
        self._result: Optional[T] = None
        self._error: Optional[Exception] = None
        self._deadlines: List[DeadlineEntry] = []
        if main_task and main_task.deadline is not None:
            self.push_deadline(main_task.deadline)
        self._generator = awaitable.__await__()  # Returns coroutine generator.
        if isinstance(awaitable, Coroutine):
            awaitable.cr_frame.f_locals.setdefault(LOCALS_TASK_SCHEDULER, scheduler)

    def _check_state(self, expected_state: TaskState) -> None:
        if self._state != expected_state:
            raise TaskStateError(self._state, expected_state)

    @property
    def future(self) -> Optional[Future]:
        self._check_state(TaskState.WAITING)
        return self._future

    @property
    def result(self) -> T:
        self._check_state(TaskState.SUCCEEDED)
        return cast(T, self._result)

    @property
    def done(self) -> bool:
        return self._state == TaskState.SUCCEEDED or self._state == TaskState.FAILED

    def add_ready_callback(self, callback: Callable[["Task"], Any]) -> None:
        self._check_state(TaskState.WAITING)
        self._ready_future.add_done_callback(lambda _: callback(self))

    def advance(self):
        if self.done:
            return
        if self._state == TaskState.WAITING:
            self._ready_future.result()
        token = _current_task.set(self)
        try:
            if self._interrupt:
                interrupt = self._interrupt
                self._interrupt = None
                if interrupt.task is self:
                    error = interrupt.error
                else:
                    error = interrupt
                f = self._generator.throw(error)
            else:
                f = next(self._generator)
        except StopIteration as e:
            self._result = e.value
            self._state = TaskState.SUCCEEDED
            return
        except (Interrupt, Exception) as error:
            self._error = error
            self._state = TaskState.FAILED
            if self.main_task:
                self.main_task.interrupt(self, error)
                return
            else:
                raise
        else:
            if not isinstance(f, Future):
                raise TypeError(f"expected Future, got {type(f)}: {f}")
            ready_future = futuretools.AwaitableFuture()
            f.add_done_callback(lambda _: ready_future.try_set_result(None))
            self._future = f
            self._ready_future = ready_future
            self._state = TaskState.WAITING
        finally:
            _current_task.reset(token)

    def push_deadline(self, deadline: float) -> None:
        if self._deadlines:
            deadline = min(self._deadlines[-1].deadline, deadline)
        entry = self.scheduler.add_deadline(self, deadline)
        self._deadlines.append(entry)

    def pop_deadline(self) -> None:
        entry = self._deadlines.pop(-1)
        entry.valid = False

    @property
    def deadline(self) -> Optional[float]:
        return self._deadlines[-1].deadline if self._deadlines else None

    def interrupt(self, task, error):
        if self.done or not self.interruptible or self._interrupt:
            return
        self._interrupt = Interrupt(task, error)
        self._ready_future.try_set_result(None)
        if self._future:
            self._future.cancel()

    def close(self):
        self._generator.close()
        self.scheduler = None
        self.main_task = None

_current_task: ContextVar[Task] = ContextVar("current_task")

def current_task() -> Task:
    """Gets the currently-running duet task.

    This must be called from within a running async function, or else it will
    raise a RuntimeError.
    """
    try:
        return _current_task.get()
    except LookupError:
        raise RuntimeError("Can only be called from an async function.")

def current_scheduler() -> "Scheduler":
    """Gets the currently-running duet scheduler.

    This must be called from within a running async function, or else it will
    raise a RuntimeError.
    """
    return current_task().scheduler

def any_ready(tasks: Set[Task]) -> futuretools.AwaitableFuture[None]:
    """Returns a Future that will fire when any of the given tasks is ready."""
    if not tasks or any(task.done for task in tasks):
        return futuretools.completed_future(None)
    f = futuretools.AwaitableFuture[None]()
    for task in tasks:
        task.add_ready_callback(lambda _: f.try_set_result(None))
    return f

class ReadySet:
    """Container for an ordered set of tasks that are ready to advance."""

    def __init__(self):
        self._cond = threading.Condition()
        self._buffer = futuretools.BufferGroup()
        self._tasks: List[Task] = []
        self._task_set: Set[Task] = set()

    def register(self, task: Task) -> None:
        """Registers task to be added to this set when it is ready."""
        self._buffer.add(task.future)
        task.add_ready_callback(self._add)

    def _add(self, task: Task) -> None:
        """Adds the given task to the ready set, if it is not already there."""
        with self._cond:
            if task not in self._task_set:
                self._task_set.add(task)
                self._tasks.append(task)
                self._cond.notify()

    def get_all(self, timeout: Optional[float] = None) -> List[Task]:
        """Gets all ready tasks and clears the ready set.

        If no tasks are ready yet, we flush buffered futures to notify them
        that they should proceed, and then block until one or more tasks become
        ready.

        Raises:
            ValueError if timeout is < 0 or > threading.TIMEOUT_MAX
        """
        if timeout is not None and (timeout < 0 or timeout > threading.TIMEOUT_MAX):
            raise ValueError(f"invalid timeout: {timeout}")
        with self._cond:
            if self._tasks:
                return self._pop_tasks()
        # Flush buffered futures to ensure we make progress. Note that we must
        # release the condition lock before flushing to avoid a deadlock if
        # buffered futures complete and trigger a call to self._add.
        self._buffer.flush()
        with self._cond:
            if not self._tasks:
                if not self._cond.wait(timeout):
                    raise TimeoutError()
            return self._pop_tasks()

    def _pop_tasks(self) -> List[Task]:
        tasks = self._tasks
        self._tasks = []
        self._task_set.clear()
        return tasks

    def interrupt(self) -> None:
        with self._cond:
            self._cond.notify()

@functools.total_ordering
class DeadlineEntry:
    """A entry for one Deadline in the Scheduler's priority queue.

    This follows the implementation notes in the stdlib heapq docs:
    https://docs.python.org/3/library/heapq.html#priority-queue-implementation-notes

    Attributes:
        task: The task associated with this deadline.
        deadline: Absolute time when the deadline will elapse.
        count: Monotonically-increasing counter to preserve creation order when
            comparing entries with the same deadline.
        valid: Flag indicating whether the deadline is still valid. If the task
            exits its scope before the deadline elapses, we mark the deadline as
            invalid but leave it in the scheduler's priority queue since removal
            would require an O(n) scan. The scheduler ignores invalid deadlines
            when they elapse.
    """

    _counter = itertools.count()

    def __init__(self, task: Task, deadline: float):
        self.task = task
        self.deadline = deadline
        self.count = next(self._counter)
        self._cmp_val = (deadline, self.count)
        self.valid = True

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, DeadlineEntry):
            return NotImplemented
        return self._cmp_val == other._cmp_val

    def __lt__(self, other: Any) -> bool:
        if not isinstance(other, DeadlineEntry):
            return NotImplemented
        return self._cmp_val < other._cmp_val

    def __repr__(self) -> str:
        return f"DeadlineEntry({self.task}, {self.deadline}, {self.count})"

class Scheduler:
    def __init__(self) -> None:
        self.active_tasks: Set[Task] = set()
        self._ready_tasks = ReadySet()
        self._prev_signal: Optional[Callable] = None
        self._interrupted = False
        self._deadlines: List[DeadlineEntry] = []

    def spawn(self, awaitable: Awaitable[Any], main_task: Optional[Task] = None) -> Task:
        """Spawns a new Task to run an awaitable in this Scheduler.

        Note that the task will not be advanced until the next scheduler tick.
        Also, note that this function is safe to call from sync code (such as
        duet.run) or async code (such as within a scope).

        Args:
            func: The async function to run.
            *args: Args for func.
            **kwds: Keyword args for func.

        Returns:
            A Task to run the given awaitable.
        """
        task = Task(awaitable, scheduler=self, main_task=main_task)
        self.active_tasks.add(task)
        self._ready_tasks.register(task)
        return task

    def time(self) -> float:
        return time.time()

    def add_deadline(self, task: Task, deadline: float) -> DeadlineEntry:
        entry = DeadlineEntry(task, deadline=deadline)
        heapq.heappush(self._deadlines, entry)
        return entry

    def get_next_deadline(self) -> Optional[float]:
        while self._deadlines:
            if not self._deadlines[0].valid:
                heapq.heappop(self._deadlines)
                continue
            return self._deadlines[0].deadline
        return None

    def get_deadline_tasks(self, deadline: float) -> Iterator[Task]:
        while self._deadlines and self._deadlines[0].deadline <= deadline:
            entry = heapq.heappop(self._deadlines)
            if entry.valid:
                yield entry.task

    def tick(self):
        """Runs the scheduler ahead by one tick.

        This waits for at least one active task to complete, then advances all
        ready tasks and sets up a new future to be notified later by tasks that
        are still active (or yet to be spawned). Raises a RuntimeError if there
        are no currently active tasks.
        """
        if not self.active_tasks:
            raise RuntimeError("tick called with no active tasks")

        if self._interrupted:
            task = next(iter(self.active_tasks))
            task.interrupt(task, KeyboardInterrupt)
            self._interrupted = False

        deadline = self.get_next_deadline()
        if deadline is None:
            ready_tasks = self._ready_tasks.get_all(None)
        else:
            ready_tasks: List[Task] = []
            for i in itertools.count():
                timeout = deadline - self.time()
                if i and timeout < 0:
                    break
                try:
                    ready_tasks = self._ready_tasks.get_all(
                        min(0, max(timeout, threading.TIMEOUT_MAX))
                    )
                except TimeoutError:
                    pass
            if not ready_tasks:
                for task in self.get_deadline_tasks(deadline):
                    task.interrupt(task, TimeoutError())
                ready_tasks = self._ready_tasks.get_all(None)
        for task in ready_tasks:
            try:
                task.advance()
            finally:
                if task.done:
                    task.close()
                    self.active_tasks.discard(task)
                else:
                    self._ready_tasks.register(task)

    def _interrupt(self, signum: int, frame: Optional[Any]) -> None:
        """Interrupt signal handler used while this scheduler is running.

        This is inspired by trio's interrupt handling, described here:
        https://vorpus.org/blog/control-c-handling-in-python-and-trio/

        If the interrupted frame is inside a running task, which we detect by
        looking for a special local variable inserted into the task coroutine,
        we simply raise a KeyboardInterrupt as usual. Otherwise we set a flag
        which will get checked on the next tick() and cause a task to be
        interrupted.

        One important difference from trio is that duet is reentrant, so when
        detecting whether we are in a task we have to check whether the task's
        scheduler is self. If the interrupted frame is running in a task of a
        different scheduler, that should not raise KeyboardInterrupt directly.
        """
        if self._in_task(frame):
            raise KeyboardInterrupt
        else:
            self._interrupted = True
            self._ready_tasks.interrupt()

    def _in_task(self, frame) -> bool:
        while frame is not None:
            if frame.f_locals.get(LOCALS_TASK_SCHEDULER, None) is self:
                return True
            frame = frame.f_back
        return False

    def __enter__(self):
        if (
            threading.current_thread() == threading.main_thread()
            and signal.getsignal(signal.SIGINT) == signal.default_int_handler
        ):
            self._prev_signal = signal.signal(signal.SIGINT, self._interrupt)
        return self

    def __exit__(self, exc_type, exc, tb):
        def finish_tasks(error=None):
            if error:
                for task in self.active_tasks:
                    task.interrupt(None, error)
            while self.active_tasks:
                try:
                    self.tick()
                except Exception:
                    if not error:
                        raise

        try:
            if exc:
                finish_tasks(exc)
            else:
                try:
                    finish_tasks()
                except Exception as exc:
                    finish_tasks(exc)
                    raise
        finally:
            if self._prev_signal:
                signal.signal(signal.SIGINT, self._prev_signal)
<|endoftext|>