mirror of
				https://github.com/enpaul/tox-poetry-installer.git
				synced 2025-11-03 07:39:20 +00:00 
			
		
		
		
	General refactoring
Fix some pseudo-hungarian type notation Fix out of date docstrings Fix arbitrary argument ordering in function signatures Remove interchangable usage of dep/dependency variable naming Remove interchangable usage of packages/package_map for same data
This commit is contained in:
		@@ -4,11 +4,9 @@ All implementations of tox hooks are defined here, as well as any single-use hel
 | 
				
			|||||||
specifically related to implementing the hooks (to keep the size/readability of the hook functions
 | 
					specifically related to implementing the hooks (to keep the size/readability of the hook functions
 | 
				
			||||||
themselves manageable).
 | 
					themselves manageable).
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from typing import List
 | 
					 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import tox
 | 
					import tox
 | 
				
			||||||
from poetry.core.packages import Package as PoetryPackage
 | 
					 | 
				
			||||||
from tox.action import Action as ToxAction
 | 
					from tox.action import Action as ToxAction
 | 
				
			||||||
from tox.config import Parser as ToxParser
 | 
					from tox.config import Parser as ToxParser
 | 
				
			||||||
from tox.venv import VirtualEnv as ToxVirtualEnv
 | 
					from tox.venv import VirtualEnv as ToxVirtualEnv
 | 
				
			||||||
@@ -92,13 +90,13 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
				
			|||||||
                f"Unlocked dependencies '{venv.envconfig.deps}' specified for environment '{venv.name}' which requires locked dependencies"
 | 
					                f"Unlocked dependencies '{venv.envconfig.deps}' specified for environment '{venv.name}' which requires locked dependencies"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        package_map: PackageMap = {
 | 
					        packages: PackageMap = {
 | 
				
			||||||
            package.name: package
 | 
					            package.name: package
 | 
				
			||||||
            for package in poetry.locker.locked_repository(True).packages
 | 
					            for package in poetry.locker.locked_repository(True).packages
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if venv.envconfig.install_dev_deps:
 | 
					        if venv.envconfig.install_dev_deps:
 | 
				
			||||||
            dev_deps = utilities.find_dev_dependencies(poetry, package_map)
 | 
					            dev_deps = utilities.find_dev_deps(packages, poetry)
 | 
				
			||||||
            tox.reporter.verbosity1(
 | 
					            tox.reporter.verbosity1(
 | 
				
			||||||
                f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
 | 
					                f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -108,15 +106,17 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
				
			|||||||
                f"{constants.REPORTER_PREFIX} Env does not install development dependencies, skipping"
 | 
					                f"{constants.REPORTER_PREFIX} Env does not install development dependencies, skipping"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        env_deps = utilities.find_env_dependencies(venv, poetry, package_map)
 | 
					        env_deps = utilities.find_additional_deps(
 | 
				
			||||||
 | 
					            packages, poetry, venv.envconfig.locked_deps
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tox.reporter.verbosity1(
 | 
					        tox.reporter.verbosity1(
 | 
				
			||||||
            f"{constants.REPORTER_PREFIX} Identified {len(env_deps)} environment dependencies to install to env"
 | 
					            f"{constants.REPORTER_PREFIX} Identified {len(env_deps)} environment dependencies to install to env"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not venv.envconfig.skip_install and not venv.envconfig.config.skipsdist:
 | 
					        if not venv.envconfig.skip_install and not venv.envconfig.config.skipsdist:
 | 
				
			||||||
            project_deps: List[PoetryPackage] = utilities.find_project_dependencies(
 | 
					            project_deps = utilities.find_project_deps(
 | 
				
			||||||
                venv, poetry, package_map
 | 
					                packages, poetry, venv.envconfig.extras
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            project_deps = []
 | 
					            project_deps = []
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,20 +21,55 @@ if typing.TYPE_CHECKING:
 | 
				
			|||||||
    from tox_poetry_installer import _poetry
 | 
					    from tox_poetry_installer import _poetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poetry":
 | 
				
			||||||
 | 
					    """Check that the local project environment meets expectations"""
 | 
				
			||||||
 | 
					    # Skip running the plugin for the provisioning environment. The provisioned environment,
 | 
				
			||||||
 | 
					    # for alternative Tox versions and/or the ``requires`` meta dependencies is specially
 | 
				
			||||||
 | 
					    # handled by Tox and is out of scope for this plugin. Since one of the ways to install this
 | 
				
			||||||
 | 
					    # plugin in the first place is via the Tox provisioning environment, it quickly becomes a
 | 
				
			||||||
 | 
					    # chicken-and-egg problem.
 | 
				
			||||||
 | 
					    if action.name == venv.envconfig.config.provision_tox_env:
 | 
				
			||||||
 | 
					        raise exceptions.SkipEnvironment(
 | 
				
			||||||
 | 
					            f"Skipping Tox provisioning env '{action.name}'"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Skip running the plugin for the packaging environment. PEP-517 front ends can handle
 | 
				
			||||||
 | 
					    # that better than we can, so let them do their thing. More to the point: if you're having
 | 
				
			||||||
 | 
					    # problems in the packaging env that this plugin would solve, god help you.
 | 
				
			||||||
 | 
					    if action.name == venv.envconfig.config.isolated_build_env:
 | 
				
			||||||
 | 
					        raise exceptions.SkipEnvironment(
 | 
				
			||||||
 | 
					            f"Skipping isolated packaging build env '{action.name}'"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from tox_poetry_installer import _poetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return _poetry.Factory().create_poetry(venv.envconfig.config.toxinidir)
 | 
				
			||||||
 | 
					    # Support running the plugin when the current tox project does not use Poetry for its
 | 
				
			||||||
 | 
					    # environment/dependency management.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ``RuntimeError`` is dangerous to blindly catch because it can be (and in Poetry's case,
 | 
				
			||||||
 | 
					    # is) raised in many different places for different purposes.
 | 
				
			||||||
 | 
					    except RuntimeError:
 | 
				
			||||||
 | 
					        raise exceptions.SkipEnvironment(
 | 
				
			||||||
 | 
					            "Project does not use Poetry for env management, skipping installation of locked dependencies"
 | 
				
			||||||
 | 
					        ) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def identify_transients(
 | 
					def identify_transients(
 | 
				
			||||||
    packages: PackageMap, dependency_name: str, allow_missing: Sequence[str] = ()
 | 
					    packages: PackageMap, dep_name: str, allow_missing: Sequence[str] = ()
 | 
				
			||||||
) -> List[PoetryPackage]:
 | 
					) -> List[PoetryPackage]:
 | 
				
			||||||
    """Using a poetry object identify all dependencies of a specific dependency
 | 
					    """Using a pool of packages, identify all transient dependencies of a given package name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param packages: All packages from the lockfile to use for identifying dependency relationships.
 | 
					    :param packages: All packages from the lockfile to use for identifying dependency relationships.
 | 
				
			||||||
    :param dependency_name: Bare name (without version) of the dependency to fetch the transient
 | 
					    :param dep_name: Bare name (without version) of the dependency to fetch the transient
 | 
				
			||||||
                            dependencies of.
 | 
					                            dependencies of.
 | 
				
			||||||
    :param allow_missing: Sequence of package names to allow to be missing from the lockfile. Any
 | 
					    :param allow_missing: Sequence of package names to allow to be missing from the lockfile. Any
 | 
				
			||||||
                          packages that are not found in the lockfile but their name appears in this
 | 
					                          packages that are not found in the lockfile but their name appears in this
 | 
				
			||||||
                          list will be silently skipped from installation.
 | 
					                          list will be silently skipped from installation.
 | 
				
			||||||
    :returns: List of packages that need to be installed for the requested dependency.
 | 
					    :returns: List of packages that need to be installed for the requested dependency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. note:: The package corresponding to the dependency named by ``dependency_name`` is included
 | 
					    .. note:: The package corresponding to the dependency named by ``dep_name`` is included
 | 
				
			||||||
              in the list of returned packages.
 | 
					              in the list of returned packages.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from tox_poetry_installer import _poetry
 | 
					    from tox_poetry_installer import _poetry
 | 
				
			||||||
@@ -90,98 +125,60 @@ def identify_transients(
 | 
				
			|||||||
            transients.append(package)
 | 
					            transients.append(package)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        find_deps_of_deps(packages[dependency_name].name)
 | 
					        find_deps_of_deps(packages[dep_name].name)
 | 
				
			||||||
    except KeyError:
 | 
					    except KeyError:
 | 
				
			||||||
        if dependency_name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
					        if dep_name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
				
			||||||
            tox.reporter.warning(
 | 
					            tox.reporter.warning(
 | 
				
			||||||
                f"{constants.REPORTER_PREFIX} Installing package '{dependency_name}' using Poetry is not supported and will be skipped"
 | 
					                f"{constants.REPORTER_PREFIX} Installing package '{dep_name}' using Poetry is not supported and will be skipped"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return []
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if any(
 | 
					        if any(
 | 
				
			||||||
            delimiter in dependency_name
 | 
					            delimiter in dep_name for delimiter in constants.PEP508_VERSION_DELIMITERS
 | 
				
			||||||
            for delimiter in constants.PEP508_VERSION_DELIMITERS
 | 
					 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            raise exceptions.LockedDepVersionConflictError(
 | 
					            raise exceptions.LockedDepVersionConflictError(
 | 
				
			||||||
                f"Locked dependency '{dependency_name}' cannot include version specifier"
 | 
					                f"Locked dependency '{dep_name}' cannot include version specifier"
 | 
				
			||||||
            ) from None
 | 
					            ) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise exceptions.LockedDepNotFoundError(
 | 
					        raise exceptions.LockedDepNotFoundError(
 | 
				
			||||||
            f"No version of locked dependency '{dependency_name}' found in the project lockfile"
 | 
					            f"No version of locked dependency '{dep_name}' found in the project lockfile"
 | 
				
			||||||
        ) from None
 | 
					        ) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return transients
 | 
					    return transients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poetry":
 | 
					def find_project_deps(
 | 
				
			||||||
    """Check that the local project environment meets expectations"""
 | 
					    packages: PackageMap, poetry: "_poetry.Poetry", extras: Sequence[str] = ()
 | 
				
			||||||
    # Skip running the plugin for the provisioning environment. The provisioned environment,
 | 
					 | 
				
			||||||
    # for alternative Tox versions and/or the ``requires`` meta dependencies is specially
 | 
					 | 
				
			||||||
    # handled by Tox and is out of scope for this plugin. Since one of the ways to install this
 | 
					 | 
				
			||||||
    # plugin in the first place is via the Tox provisioning environment, it quickly becomes a
 | 
					 | 
				
			||||||
    # chicken-and-egg problem.
 | 
					 | 
				
			||||||
    if action.name == venv.envconfig.config.provision_tox_env:
 | 
					 | 
				
			||||||
        raise exceptions.SkipEnvironment(
 | 
					 | 
				
			||||||
            f"Skipping Tox provisioning env '{action.name}'"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Skip running the plugin for the packaging environment. PEP-517 front ends can handle
 | 
					 | 
				
			||||||
    # that better than we can, so let them do their thing. More to the point: if you're having
 | 
					 | 
				
			||||||
    # problems in the packaging env that this plugin would solve, god help you.
 | 
					 | 
				
			||||||
    if action.name == venv.envconfig.config.isolated_build_env:
 | 
					 | 
				
			||||||
        raise exceptions.SkipEnvironment(
 | 
					 | 
				
			||||||
            f"Skipping isolated packaging build env '{action.name}'"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    from tox_poetry_installer import _poetry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        return _poetry.Factory().create_poetry(venv.envconfig.config.toxinidir)
 | 
					 | 
				
			||||||
    # Support running the plugin when the current tox project does not use Poetry for its
 | 
					 | 
				
			||||||
    # environment/dependency management.
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # ``RuntimeError`` is dangerous to blindly catch because it can be (and in Poetry's case,
 | 
					 | 
				
			||||||
    # is) raised in many different places for different purposes.
 | 
					 | 
				
			||||||
    except RuntimeError:
 | 
					 | 
				
			||||||
        raise exceptions.SkipEnvironment(
 | 
					 | 
				
			||||||
            "Project does not use Poetry for env management, skipping installation of locked dependencies"
 | 
					 | 
				
			||||||
        ) from None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def find_project_dependencies(
 | 
					 | 
				
			||||||
    venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
 | 
					 | 
				
			||||||
) -> List[PoetryPackage]:
 | 
					) -> List[PoetryPackage]:
 | 
				
			||||||
    """Find the root package dependencies
 | 
					    """Find the root project dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Recursively identify the root package dependencies
 | 
					    Recursively identify the dependencies of the root project package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param venv: Tox virtual environment to install the packages to
 | 
					    :param packages: Mapping of all locked package names to their corresponding package object
 | 
				
			||||||
    :param poetry: Poetry object the packages were sourced from
 | 
					    :param poetry: Poetry object for the current project
 | 
				
			||||||
    :param packages: Mapping of package names to the corresponding package object
 | 
					    :param extras: Sequence of extra names to include the dependencies of
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base_dependencies: List[PoetryPackage] = [
 | 
					    base_deps: List[PoetryPackage] = [
 | 
				
			||||||
        packages[item.name]
 | 
					        packages[item.name]
 | 
				
			||||||
        for item in poetry.package.requires
 | 
					        for item in poetry.package.requires
 | 
				
			||||||
        if not item.is_optional()
 | 
					        if not item.is_optional()
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extra_dependencies: List[PoetryPackage] = []
 | 
					    extra_deps: List[PoetryPackage] = []
 | 
				
			||||||
    for extra in venv.envconfig.extras:
 | 
					    for extra in extras:
 | 
				
			||||||
        tox.reporter.verbosity1(
 | 
					        tox.reporter.verbosity1(
 | 
				
			||||||
            f"{constants.REPORTER_PREFIX} Processing project extra '{extra}'"
 | 
					            f"{constants.REPORTER_PREFIX} Processing project extra '{extra}'"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            extra_dependencies += [
 | 
					            extra_deps += [packages[item.name] for item in poetry.package.extras[extra]]
 | 
				
			||||||
                packages[item.name] for item in poetry.package.extras[extra]
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        except KeyError:
 | 
					        except KeyError:
 | 
				
			||||||
            raise exceptions.ExtraNotFoundError(
 | 
					            raise exceptions.ExtraNotFoundError(
 | 
				
			||||||
                f"Environment '{venv.name}' specifies project extra '{extra}' which was not found in the lockfile"
 | 
					                f"Environment specifies project extra '{extra}' which was not found in the lockfile"
 | 
				
			||||||
            ) from None
 | 
					            ) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies: List[PoetryPackage] = []
 | 
					    dependencies: List[PoetryPackage] = []
 | 
				
			||||||
    for dep in base_dependencies + extra_dependencies:
 | 
					    for dep in base_deps + extra_deps:
 | 
				
			||||||
        dependencies += identify_transients(
 | 
					        dependencies += identify_transients(
 | 
				
			||||||
            packages, dep.name.lower(), allow_missing=[poetry.package.name]
 | 
					            packages, dep.name.lower(), allow_missing=[poetry.package.name]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -189,43 +186,39 @@ def find_project_dependencies(
 | 
				
			|||||||
    return dependencies
 | 
					    return dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_dev_dependencies(
 | 
					def find_additional_deps(
 | 
				
			||||||
    poetry: "_poetry.Poetry", packages: PackageMap
 | 
					    packages: PackageMap, poetry: "_poetry.Poetry", dep_names: Sequence[str]
 | 
				
			||||||
 | 
					) -> List[PoetryPackage]:
 | 
				
			||||||
 | 
					    """Find additional dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Recursively identify the dependencies of an arbitrary list of package names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param packages: Mapping of all locked package names to their corresponding package object
 | 
				
			||||||
 | 
					    :param poetry: Poetry object for the current project
 | 
				
			||||||
 | 
					    :param dep_names: Sequence of additional dependency names to recursively find the transient
 | 
				
			||||||
 | 
					                      dependencies for
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    deps: List[PoetryPackage] = []
 | 
				
			||||||
 | 
					    for dep_name in dep_names:
 | 
				
			||||||
 | 
					        deps += identify_transients(
 | 
				
			||||||
 | 
					            packages, dep_name.lower(), allow_missing=[poetry.package.name]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return deps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def find_dev_deps(
 | 
				
			||||||
 | 
					    packages: PackageMap, poetry: "_poetry.Poetry"
 | 
				
			||||||
) -> List[PoetryPackage]:
 | 
					) -> List[PoetryPackage]:
 | 
				
			||||||
    """Find the dev dependencies
 | 
					    """Find the dev dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Recursively identify the Poetry dev dependencies
 | 
					    Recursively identify the Poetry dev dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param venv: Tox virtual environment to install the packages to
 | 
					    :param packages: Mapping of all locked package names to their corresponding package object
 | 
				
			||||||
    :param poetry: Poetry object the packages were sourced from
 | 
					    :param poetry: Poetry object for the current project
 | 
				
			||||||
    :param packages: Mapping of package names to the corresponding package object
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    dependencies: List[PoetryPackage] = []
 | 
					    return find_additional_deps(
 | 
				
			||||||
    for dep_name in (
 | 
					        packages,
 | 
				
			||||||
        poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys()
 | 
					        poetry,
 | 
				
			||||||
    ):
 | 
					        poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys(),
 | 
				
			||||||
        dependencies += identify_transients(
 | 
					 | 
				
			||||||
            packages, dep_name, allow_missing=[poetry.package.name]
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return dependencies
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def find_env_dependencies(
 | 
					 | 
				
			||||||
    venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
 | 
					 | 
				
			||||||
) -> List[PoetryPackage]:
 | 
					 | 
				
			||||||
    """Find the environment dependencies
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Recursively identify the dependencies to install for the current environment
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param venv: Tox virtual environment to install the packages to
 | 
					 | 
				
			||||||
    :param poetry: Poetry object the packages were sourced from
 | 
					 | 
				
			||||||
    :param packages: Mapping of package names to the corresponding package object
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    dependencies: List[PoetryPackage] = []
 | 
					 | 
				
			||||||
    for dep in venv.envconfig.locked_deps:
 | 
					 | 
				
			||||||
        dependencies += identify_transients(
 | 
					 | 
				
			||||||
            packages, dep.lower(), allow_missing=[poetry.package.name]
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return dependencies
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user