Current File : //proc/self/root/usr/lib/python3/dist-packages/twisted/python/test/test_deprecate.py
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for Twisted's deprecation framework, L{twisted.python.deprecate}.
"""


import inspect
import sys
import types
import warnings
from os.path import normcase
from warnings import catch_warnings, simplefilter

try:
    from importlib import invalidate_caches
except ImportError:
    invalidate_caches = None  # type: ignore[assignment]

from incremental import Version

from twisted.python import deprecate
from twisted.python.deprecate import (
    DEPRECATION_WARNING_FORMAT,
    _appendToDocstring,
    _fullyQualifiedName as fullyQualifiedName,
    _getDeprecationDocstring,
    _getDeprecationWarningString,
    _mutuallyExclusiveArguments,
    _passedArgSpec,
    _passedSignature,
    deprecated,
    deprecatedKeywordParameter,
    deprecatedProperty,
    getDeprecationWarningString,
)
from twisted.python.filepath import FilePath
from twisted.python.runtime import platform
from twisted.python.test import deprecatedattributes
from twisted.python.test.modules_helpers import TwistedModulesMixin
from twisted.trial.unittest import SynchronousTestCase

# Note that various tests in this module require manual encoding of paths to
# utf-8. This can be fixed once FilePath supports Unicode; see #2366, #4736,
# #5203.


class _MockDeprecatedAttribute:
    """
    Mock of L{twisted.python.deprecate._DeprecatedAttribute}.

    @ivar value: The value of the attribute.
    """

    def __init__(self, value):
        self.value = value

    def get(self):
        """
        Get a known value.
        """
        return self.value


class ModuleProxyTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate._ModuleProxy}, which proxies
    access to module-level attributes, intercepting access to deprecated
    attributes and passing through access to normal attributes.
    """

    def _makeProxy(self, **attrs):
        """
        Create a temporary module proxy object.

        @param **kw: Attributes to initialise on the temporary module object

        @rtype: L{twistd.python.deprecate._ModuleProxy}
        """
        mod = types.ModuleType("foo")
        for key, value in attrs.items():
            setattr(mod, key, value)
        return deprecate._ModuleProxy(mod)

    def test_getattrPassthrough(self):
        """
        Getting a normal attribute on a L{twisted.python.deprecate._ModuleProxy}
        retrieves the underlying attribute's value, and raises C{AttributeError}
        if a non-existent attribute is accessed.
        """
        proxy = self._makeProxy(SOME_ATTRIBUTE="hello")
        self.assertIs(proxy.SOME_ATTRIBUTE, "hello")
        self.assertRaises(AttributeError, getattr, proxy, "DOES_NOT_EXIST")

    def test_getattrIntercept(self):
        """
        Getting an attribute marked as being deprecated on
        L{twisted.python.deprecate._ModuleProxy} results in calling the
        deprecated wrapper's C{get} method.
        """
        proxy = self._makeProxy()
        _deprecatedAttributes = object.__getattribute__(proxy, "_deprecatedAttributes")
        _deprecatedAttributes["foo"] = _MockDeprecatedAttribute(42)
        self.assertEqual(proxy.foo, 42)

    def test_privateAttributes(self):
        """
        Private attributes of L{twisted.python.deprecate._ModuleProxy} are
        inaccessible when regular attribute access is used.
        """
        proxy = self._makeProxy()
        self.assertRaises(AttributeError, getattr, proxy, "_module")
        self.assertRaises(AttributeError, getattr, proxy, "_deprecatedAttributes")

    def test_setattr(self):
        """
        Setting attributes on L{twisted.python.deprecate._ModuleProxy} proxies
        them through to the wrapped module.
        """
        proxy = self._makeProxy()
        proxy._module = 1
        self.assertNotEqual(object.__getattribute__(proxy, "_module"), 1)
        self.assertEqual(proxy._module, 1)

    def test_repr(self):
        """
        L{twisted.python.deprecated._ModuleProxy.__repr__} produces a string
        containing the proxy type and a representation of the wrapped module
        object.
        """
        proxy = self._makeProxy()
        realModule = object.__getattribute__(proxy, "_module")
        self.assertEqual(repr(proxy), f"<{type(proxy).__name__} module={realModule!r}>")


class DeprecatedAttributeTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate._DeprecatedAttribute} and
    L{twisted.python.deprecate.deprecatedModuleAttribute}, which issue
    warnings for deprecated module-level attributes.
    """

    def setUp(self):
        self.version = deprecatedattributes.version
        self.message = deprecatedattributes.message
        self._testModuleName = __name__ + ".foo"

    def _getWarningString(self, attr):
        """
        Create the warning string used by deprecated attributes.
        """
        return _getDeprecationWarningString(
            deprecatedattributes.__name__ + "." + attr,
            deprecatedattributes.version,
            DEPRECATION_WARNING_FORMAT + ": " + deprecatedattributes.message,
        )

    def test_deprecatedAttributeHelper(self):
        """
        L{twisted.python.deprecate._DeprecatedAttribute} correctly sets its
        __name__ to match that of the deprecated attribute and emits a warning
        when the original attribute value is accessed.
        """
        name = "ANOTHER_DEPRECATED_ATTRIBUTE"
        setattr(deprecatedattributes, name, 42)
        attr = deprecate._DeprecatedAttribute(
            deprecatedattributes, name, self.version, self.message
        )

        self.assertEqual(attr.__name__, name)

        # Since we're accessing the value getter directly, as opposed to via
        # the module proxy, we need to match the warning's stack level.
        def addStackLevel():
            attr.get()

        # Access the deprecated attribute.
        addStackLevel()
        warningsShown = self.flushWarnings([self.test_deprecatedAttributeHelper])
        self.assertIs(warningsShown[0]["category"], DeprecationWarning)
        self.assertEqual(warningsShown[0]["message"], self._getWarningString(name))
        self.assertEqual(len(warningsShown), 1)

    def test_deprecatedAttribute(self):
        """
        L{twisted.python.deprecate.deprecatedModuleAttribute} wraps a
        module-level attribute in an object that emits a deprecation warning
        when it is accessed the first time only, while leaving other unrelated
        attributes alone.
        """
        # Accessing non-deprecated attributes does not issue a warning.
        deprecatedattributes.ANOTHER_ATTRIBUTE
        warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
        self.assertEqual(len(warningsShown), 0)

        name = "DEPRECATED_ATTRIBUTE"

        # Access the deprecated attribute. This uses getattr to avoid repeating
        # the attribute name.
        getattr(deprecatedattributes, name)

        warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
        self.assertEqual(len(warningsShown), 1)
        self.assertIs(warningsShown[0]["category"], DeprecationWarning)
        self.assertEqual(warningsShown[0]["message"], self._getWarningString(name))

    def test_wrappedModule(self):
        """
        Deprecating an attribute in a module replaces and wraps that module
        instance, in C{sys.modules}, with a
        L{twisted.python.deprecate._ModuleProxy} instance but only if it hasn't
        already been wrapped.
        """
        sys.modules[self._testModuleName] = mod = types.ModuleType("foo")
        self.addCleanup(sys.modules.pop, self._testModuleName)

        setattr(mod, "first", 1)
        setattr(mod, "second", 2)

        deprecate.deprecatedModuleAttribute(
            Version("Twisted", 8, 0, 0), "message", self._testModuleName, "first"
        )

        proxy = sys.modules[self._testModuleName]
        self.assertNotEqual(proxy, mod)

        deprecate.deprecatedModuleAttribute(
            Version("Twisted", 8, 0, 0), "message", self._testModuleName, "second"
        )

        self.assertIs(proxy, sys.modules[self._testModuleName])


class ImportedModuleAttributeTests(TwistedModulesMixin, SynchronousTestCase):
    """
    Tests for L{deprecatedModuleAttribute} which involve loading a module via
    'import'.
    """

    _packageInit = """\
from twisted.python.deprecate import deprecatedModuleAttribute
from incremental import Version

deprecatedModuleAttribute(
    Version('Package', 1, 2, 3), 'message', __name__, 'module')
"""

    def pathEntryTree(self, tree):
        """
        Create some files in a hierarchy, based on a dictionary describing those
        files.  The resulting hierarchy will be placed onto sys.path for the
        duration of the test.

        @param tree: A dictionary representing a directory structure.  Keys are
            strings, representing filenames, dictionary values represent
            directories, string values represent file contents.

        @return: another dictionary similar to the input, with file content
            strings replaced with L{FilePath} objects pointing at where those
            contents are now stored.
        """

        def makeSomeFiles(pathobj, dirdict):
            pathdict = {}
            for key, value in dirdict.items():
                child = pathobj.child(key)
                if isinstance(value, bytes):
                    pathdict[key] = child
                    child.setContent(value)
                elif isinstance(value, dict):
                    child.createDirectory()
                    pathdict[key] = makeSomeFiles(child, value)
                else:
                    raise ValueError("only strings and dicts allowed as values")
            return pathdict

        base = FilePath(self.mktemp().encode("utf-8"))
        base.makedirs()

        result = makeSomeFiles(base, tree)
        # On Python 3, sys.path cannot include byte paths:
        self.replaceSysPath([base.path.decode("utf-8")] + sys.path)
        self.replaceSysModules(sys.modules.copy())
        return result

    def simpleModuleEntry(self):
        """
        Add a sample module and package to the path, returning a L{FilePath}
        pointing at the module which will be loadable as C{package.module}.
        """
        paths = self.pathEntryTree(
            {
                b"package": {
                    b"__init__.py": self._packageInit.encode("utf-8"),
                    b"module.py": b"",
                }
            }
        )
        return paths[b"package"][b"module.py"]

    def checkOneWarning(self, modulePath):
        """
        Verification logic for L{test_deprecatedModule}.
        """
        from package import module  # type: ignore[import-not-found]

        self.assertEqual(FilePath(module.__file__.encode("utf-8")), modulePath)
        emitted = self.flushWarnings([self.checkOneWarning])
        self.assertEqual(len(emitted), 1)
        self.assertEqual(
            emitted[0]["message"],
            "package.module was deprecated in Package 1.2.3: " "message",
        )
        self.assertEqual(emitted[0]["category"], DeprecationWarning)

    def test_deprecatedModule(self):
        """
        If L{deprecatedModuleAttribute} is used to deprecate a module attribute
        of a package, only one deprecation warning is emitted when the
        deprecated module is imported.
        """
        self.checkOneWarning(self.simpleModuleEntry())

    def test_deprecatedModuleMultipleTimes(self):
        """
        If L{deprecatedModuleAttribute} is used to deprecate a module attribute
        of a package, only one deprecation warning is emitted when the
        deprecated module is subsequently imported.
        """
        mp = self.simpleModuleEntry()
        # The first time, the code needs to be loaded.
        self.checkOneWarning(mp)
        # The second time, things are slightly different; the object's already
        # in the namespace.
        self.checkOneWarning(mp)
        # The third and fourth times, things things should all be exactly the
        # same, but this is a sanity check to make sure the implementation isn't
        # special casing the second time.  Also, putting these cases into a loop
        # means that the stack will be identical, to make sure that the
        # implementation doesn't rely too much on stack-crawling.
        for x in range(2):
            self.checkOneWarning(mp)


class WarnAboutFunctionTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate.warnAboutFunction} which allows the
    callers of a function to issue a C{DeprecationWarning} about that function.
    """

    def setUp(self):
        """
        Create a file that will have known line numbers when emitting warnings.
        """
        self.package = FilePath(self.mktemp()).child("twisted_private_helper")
        self.package.makedirs()
        self.package.child("__init__.py").setContent(b"")
        self.package.child("module.py").setContent(
            b"""
"A module string"

from twisted.python import deprecate

def testFunction():
    "A doc string"
    a = 1 + 2
    return a

def callTestFunction():
    b = testFunction()
    if b == 3:
        deprecate.warnAboutFunction(testFunction, "A Warning String")
"""
        )
        self.package.child("pep626.py").setContent(
            b"""
"A module string"

from twisted.python import deprecate

def noop():
    pass

def testFunction(a=1, b=1):
    "A doc string"
    if a:
        if b:
            noop()
        else:
            pass

def callTestFunction():
    b = testFunction()
    if b is None:
        deprecate.warnAboutFunction(testFunction, "A Warning String")
"""
        )
        # Python 3 doesn't accept bytes in sys.path:
        packagePath = self.package.parent().path
        sys.path.insert(0, packagePath)
        self.addCleanup(sys.path.remove, packagePath)

        modules = sys.modules.copy()
        self.addCleanup(lambda: (sys.modules.clear(), sys.modules.update(modules)))

        # On Windows, most FilePath interactions produce
        # DeprecationWarnings, so flush them here so that they don't interfere
        # with the tests.
        if platform.isWindows():
            self.flushWarnings()

    def test_warning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning the file and line number
        of which point to the beginning of the implementation of the function
        passed to it.
        """

        def aFunc():
            pass

        deprecate.warnAboutFunction(aFunc, "A Warning Message")
        warningsShown = self.flushWarnings()
        filename = __file__
        if filename.lower().endswith(".pyc"):
            filename = filename[:-1]
        self.assertSamePath(FilePath(warningsShown[0]["filename"]), FilePath(filename))
        self.assertEqual(warningsShown[0]["message"], "A Warning Message")

    def test_warningLineNumber(self):
        """
        L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
        number of a line within the implementation of the function passed to it.
        """
        from twisted_private_helper import module  # type: ignore[import-not-found]

        module.callTestFunction()
        warningsShown = self.flushWarnings()
        self.assertSamePath(
            FilePath(warningsShown[0]["filename"].encode("utf-8")),
            self.package.sibling(b"twisted_private_helper").child(b"module.py"),
        )
        # Line number 9 is the last line in the testFunction in the helper
        # module.
        self.assertEqual(warningsShown[0]["lineno"], 9)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def test_warningLineNumberDisFindlinestarts(self):
        """
        L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
        number of a line within the implementation handling the case in which
        dis.findlinestarts returns the lines in random order.
        """
        from twisted_private_helper import pep626

        pep626.callTestFunction()
        warningsShown = self.flushWarnings()
        self.assertSamePath(
            FilePath(warningsShown[0]["filename"].encode("utf-8")),
            self.package.sibling(b"twisted_private_helper").child(b"pep626.py"),
        )
        # Line number 15 is the last line in the testFunction in the helper
        # module.
        self.assertEqual(warningsShown[0]["lineno"], 15)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def assertSamePath(self, first, second):
        """
        Assert that the two paths are the same, considering case normalization
        appropriate for the current platform.

        @type first: L{FilePath}
        @type second: L{FilePath}

        @raise C{self.failureType}: If the paths are not the same.
        """
        self.assertTrue(
            normcase(first.path) == normcase(second.path),
            f"{first!r} != {second!r}",
        )

    def test_renamedFile(self):
        """
        Even if the implementation of a deprecated function is moved around on
        the filesystem, the line number in the warning emitted by
        L{deprecate.warnAboutFunction} points to a line in the implementation of
        the deprecated function.
        """
        from twisted_private_helper import module

        # Clean up the state resulting from that import; we're not going to use
        # this module, so it should go away.
        del sys.modules["twisted_private_helper"]
        del sys.modules[module.__name__]

        # Rename the source directory
        self.package.moveTo(self.package.sibling(b"twisted_renamed_helper"))

        # Make sure importlib notices we've changed importable packages:
        if invalidate_caches:  # type: ignore[truthy-function]
            invalidate_caches()

        # Import the newly renamed version
        from twisted_renamed_helper import module  # type: ignore[import-not-found]

        self.addCleanup(sys.modules.pop, "twisted_renamed_helper")
        self.addCleanup(sys.modules.pop, module.__name__)

        module.callTestFunction()
        warningsShown = self.flushWarnings([module.testFunction])
        warnedPath = FilePath(warningsShown[0]["filename"].encode("utf-8"))
        expectedPath = self.package.sibling(b"twisted_renamed_helper").child(
            b"module.py"
        )
        self.assertSamePath(warnedPath, expectedPath)
        self.assertEqual(warningsShown[0]["lineno"], 9)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def test_filteredWarning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning that will be filtered if
        L{warnings.filterwarning} is called with the module name of the
        deprecated function.
        """
        # Clean up anything *else* that might spuriously filter out the warning,
        # such as the "always" simplefilter set up by unittest._collectWarnings.
        # We'll also rely on trial to restore the original filters afterwards.
        del warnings.filters[:]

        warnings.filterwarnings(action="ignore", module="twisted_private_helper")

        from twisted_private_helper import module

        module.callTestFunction()

        warningsShown = self.flushWarnings()
        self.assertEqual(len(warningsShown), 0)

    def test_filteredOnceWarning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning that will be filtered
        once if L{warnings.filterwarning} is called with the module name of the
        deprecated function and an action of once.
        """
        # Clean up anything *else* that might spuriously filter out the warning,
        # such as the "always" simplefilter set up by unittest._collectWarnings.
        # We'll also rely on trial to restore the original filters afterwards.
        del warnings.filters[:]

        warnings.filterwarnings(action="module", module="twisted_private_helper")

        from twisted_private_helper import module

        module.callTestFunction()
        module.callTestFunction()

        warningsShown = self.flushWarnings()
        self.assertEqual(len(warningsShown), 1)
        message = warningsShown[0]["message"]
        category = warningsShown[0]["category"]
        filename = warningsShown[0]["filename"]
        lineno = warningsShown[0]["lineno"]
        msg = warnings.formatwarning(message, category, filename, lineno)
        self.assertTrue(
            msg.endswith(
                "module.py:9: DeprecationWarning: A Warning String\n" "  return a\n"
            ),
            f"Unexpected warning string: {msg!r}",
        )


def dummyCallable():
    """
    Do nothing.

    This is used to test the deprecation decorators.
    """


def dummyReplacementMethod():
    """
    Do nothing.

    This is used to test the replacement parameter to L{deprecated}.
    """


class DeprecationWarningsTests(SynchronousTestCase):
    def test_getDeprecationWarningString(self):
        """
        L{getDeprecationWarningString} returns a string that tells us that a
        callable was deprecated at a certain released version of Twisted.
        """
        version = Version("Twisted", 8, 0, 0)
        self.assertEqual(
            getDeprecationWarningString(self.test_getDeprecationWarningString, version),
            "%s.DeprecationWarningsTests.test_getDeprecationWarningString "
            "was deprecated in Twisted 8.0.0" % (__name__,),
        )

    def test_getDeprecationWarningStringWithFormat(self):
        """
        L{getDeprecationWarningString} returns a string that tells us that a
        callable was deprecated at a certain released version of Twisted, with
        a message containing additional information about the deprecation.
        """
        version = Version("Twisted", 8, 0, 0)
        format = DEPRECATION_WARNING_FORMAT + ": This is a message"
        self.assertEqual(
            getDeprecationWarningString(
                self.test_getDeprecationWarningString, version, format
            ),
            "%s.DeprecationWarningsTests.test_getDeprecationWarningString was "
            "deprecated in Twisted 8.0.0: This is a message" % (__name__,),
        )

    def test_deprecateEmitsWarning(self):
        """
        Decorating a callable with L{deprecated} emits a warning.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)

        def addStackLevel():
            dummy()

        with catch_warnings(record=True) as caught:
            simplefilter("always")
            addStackLevel()
            self.assertEqual(caught[0].category, DeprecationWarning)
            self.assertEqual(
                str(caught[0].message),
                getDeprecationWarningString(dummyCallable, version),
            )
            # rstrip in case .pyc/.pyo
            self.assertEqual(caught[0].filename.rstrip("co"), __file__.rstrip("co"))

    def test_deprecatedPreservesName(self):
        """
        The decorated function has the same name as the original.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)
        self.assertEqual(dummyCallable.__name__, dummy.__name__)
        self.assertEqual(fullyQualifiedName(dummyCallable), fullyQualifiedName(dummy))

    def test_getDeprecationDocstring(self):
        """
        L{_getDeprecationDocstring} returns a note about the deprecation to go
        into a docstring.
        """
        version = Version("Twisted", 8, 0, 0)
        self.assertEqual(
            "Deprecated in Twisted 8.0.0.", _getDeprecationDocstring(version, "")
        )

    def test_deprecatedUpdatesDocstring(self):
        """
        The docstring of the deprecated function is appended with information
        about the deprecation.
        """

        def localDummyCallable():
            """
            Do nothing.

            This is used to test the deprecation decorators.
            """

        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(localDummyCallable)

        _appendToDocstring(localDummyCallable, _getDeprecationDocstring(version, ""))

        self.assertEqual(localDummyCallable.__doc__, dummy.__doc__)

    def test_versionMetadata(self):
        """
        Deprecating a function adds version information to the decorated
        version of that function.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)
        self.assertEqual(version, dummy.deprecatedVersion)

    def test_getDeprecationWarningStringReplacement(self):
        """
        L{getDeprecationWarningString} takes an additional replacement parameter
        that can be used to add information to the deprecation.  If the
        replacement parameter is a string, it will be interpolated directly into
        the result.
        """
        version = Version("Twisted", 8, 0, 0)
        warningString = getDeprecationWarningString(
            self.test_getDeprecationWarningString,
            version,
            replacement="something.foobar",
        )
        self.assertEqual(
            warningString,
            "%s was deprecated in Twisted 8.0.0; please use something.foobar "
            "instead" % (fullyQualifiedName(self.test_getDeprecationWarningString),),
        )

    def test_getDeprecationWarningStringReplacementWithCallable(self):
        """
        L{getDeprecationWarningString} takes an additional replacement parameter
        that can be used to add information to the deprecation. If the
        replacement parameter is a callable, its fully qualified name will be
        interpolated into the result.
        """
        version = Version("Twisted", 8, 0, 0)
        warningString = getDeprecationWarningString(
            self.test_getDeprecationWarningString,
            version,
            replacement=dummyReplacementMethod,
        )
        self.assertEqual(
            warningString,
            "%s was deprecated in Twisted 8.0.0; please use "
            "%s.dummyReplacementMethod instead"
            % (fullyQualifiedName(self.test_getDeprecationWarningString), __name__),
        )


@deprecated(Version("Twisted", 1, 2, 3))
class DeprecatedClass:
    """
    Class which is entirely deprecated without having a replacement.
    """


class ClassWithDeprecatedProperty:
    """
    Class with a single deprecated property.
    """

    _someProtectedValue = None

    @deprecatedProperty(Version("Twisted", 1, 2, 3))
    def someProperty(self):
        """
        Getter docstring.

        @return: The property.
        """
        return self._someProtectedValue

    @someProperty.setter  # type: ignore[no-redef]
    def someProperty(self, value):
        """
        Setter docstring.
        """
        self._someProtectedValue = value


@deprecatedKeywordParameter(Version("Twisted", 19, 2, 0), "foo")
def functionWithDeprecatedParameter(a, b, c=1, foo=2, bar=3):
    """
    Function with a deprecated keyword parameter.
    """


class DeprecatedDecoratorTests(SynchronousTestCase):
    """
    Tests for deprecated decorators.
    """

    def assertDocstring(self, target, expected):
        """
        Check that C{target} object has the C{expected} docstring lines.

        @param target: Object which is checked.
        @type target: C{anything}

        @param expected: List of lines, ignoring empty lines or leading or
            trailing spaces.
        @type expected: L{list} or L{str}
        """
        self.assertEqual(
            expected, [x.strip() for x in target.__doc__.splitlines() if x.strip()]
        )

    def test_propertyGetter(self):
        """
        When L{deprecatedProperty} is used on a C{property}, accesses raise a
        L{DeprecationWarning} and getter docstring is updated to inform the
        version in which it was deprecated. C{deprecatedVersion} attribute is
        also set to inform the deprecation version.
        """
        obj = ClassWithDeprecatedProperty()

        obj.someProperty

        self.assertDocstring(
            ClassWithDeprecatedProperty.someProperty,
            [
                "Getter docstring.",
                "@return: The property.",
                "Deprecated in Twisted 1.2.3.",
            ],
        )
        ClassWithDeprecatedProperty.someProperty.deprecatedVersion = Version(
            "Twisted", 1, 2, 3
        )

        message = (
            "twisted.python.test.test_deprecate.ClassWithDeprecatedProperty."
            "someProperty was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_propertyGetter])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_propertySetter(self):
        """
        When L{deprecatedProperty} is used on a C{property}, setter accesses
        raise a L{DeprecationWarning}.
        """
        newValue = object()
        obj = ClassWithDeprecatedProperty()

        obj.someProperty = newValue

        self.assertIs(newValue, obj._someProtectedValue)
        message = (
            "twisted.python.test.test_deprecate.ClassWithDeprecatedProperty."
            "someProperty was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_propertySetter])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_class(self):
        """
        When L{deprecated} is used on a class, instantiations raise a
        L{DeprecationWarning} and class's docstring is updated to inform the
        version in which it was deprecated. C{deprecatedVersion} attribute is
        also set to inform the deprecation version.
        """
        DeprecatedClass()

        self.assertDocstring(
            DeprecatedClass,
            [
                ("Class which is entirely deprecated without having a " "replacement."),
                "Deprecated in Twisted 1.2.3.",
            ],
        )
        DeprecatedClass.deprecatedVersion = Version("Twisted", 1, 2, 3)

        message = (
            "twisted.python.test.test_deprecate.DeprecatedClass "
            "was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_class])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_deprecatedReplacement(self):
        """
        L{deprecated} takes an additional replacement parameter that can be used
        to indicate the new, non-deprecated method developers should use.  If
        the replacement parameter is a string, it will be interpolated directly
        into the warning message.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version, "something.foobar")(dummyCallable)
        self.assertEqual(
            dummy.__doc__,
            "\n"
            "    Do nothing.\n\n"
            "    This is used to test the deprecation decorators.\n\n"
            "    Deprecated in Twisted 8.0.0; please use "
            "something.foobar"
            " instead.\n"
            "    ",
        )

    def test_deprecatedReplacementWithCallable(self):
        """
        L{deprecated} takes an additional replacement parameter that can be used
        to indicate the new, non-deprecated method developers should use.  If
        the replacement parameter is a callable, its fully qualified name will
        be interpolated into the warning message.
        """
        version = Version("Twisted", 8, 0, 0)
        decorator = deprecated(version, replacement=dummyReplacementMethod)
        dummy = decorator(dummyCallable)
        self.assertEqual(
            dummy.__doc__,
            "\n"
            "    Do nothing.\n\n"
            "    This is used to test the deprecation decorators.\n\n"
            "    Deprecated in Twisted 8.0.0; please use "
            "%s.dummyReplacementMethod instead.\n"
            "    " % (__name__,),
        )

    def test_deprecatedKeywordParameter(self):
        message = (
            "The 'foo' parameter to "
            "twisted.python.test.test_deprecate."
            "functionWithDeprecatedParameter "
            "was deprecated in Twisted 19.2.0"
        )

        with catch_warnings(record=True) as ws:
            simplefilter("always")

            functionWithDeprecatedParameter(10, 20)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, 30)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, foo=40)
            self.assertEqual(len(ws), 1)
            self.assertEqual(ws[0].category, DeprecationWarning)
            self.assertEqual(str(ws[0].message), message)

            ws.clear()
            functionWithDeprecatedParameter(10, 20, bar=50)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, 30, 40)
            self.assertEqual(len(ws), 1)
            self.assertEqual(ws[0].category, DeprecationWarning)
            self.assertEqual(str(ws[0].message), message)


class AppendToDocstringTests(SynchronousTestCase):
    """
    Test the _appendToDocstring function.

    _appendToDocstring is used to add text to a docstring.
    """

    def test_appendToEmptyDocstring(self):
        """
        Appending to an empty docstring simply replaces the docstring.
        """

        def noDocstring():
            pass

        _appendToDocstring(noDocstring, "Appended text.")
        self.assertEqual("Appended text.", noDocstring.__doc__)

    def test_appendToSingleLineDocstring(self):
        """
        Appending to a single line docstring places the message on a new line,
        with a blank line separating it from the rest of the docstring.

        The docstring ends with a newline, conforming to Twisted and PEP 8
        standards. Unfortunately, the indentation is incorrect, since the
        existing docstring doesn't have enough info to help us indent
        properly.
        """

        def singleLineDocstring():
            """This doesn't comply with standards, but is here for a test."""

        _appendToDocstring(singleLineDocstring, "Appended text.")
        self.assertEqual(
            [
                "This doesn't comply with standards, but is here for a test.",
                "",
                "Appended text.",
            ],
            singleLineDocstring.__doc__.splitlines(),
        )
        self.assertTrue(singleLineDocstring.__doc__.endswith("\n"))

    def test_appendToMultilineDocstring(self):
        """
        Appending to a multi-line docstring places the messade on a new line,
        with a blank line separating it from the rest of the docstring.

        Because we have multiple lines, we have enough information to do
        indentation.
        """

        def multiLineDocstring():
            """
            This is a multi-line docstring.
            """

        def expectedDocstring():
            """
            This is a multi-line docstring.

            Appended text.
            """

        _appendToDocstring(multiLineDocstring, "Appended text.")
        self.assertEqual(expectedDocstring.__doc__, multiLineDocstring.__doc__)


class MutualArgumentExclusionTests(SynchronousTestCase):
    """
    Tests for L{mutuallyExclusiveArguments}.
    """

    def checkPassed(self, func, *args, **kw):
        """
        Test an invocation of L{passed} with the given function, arguments, and
        keyword arguments.

        @param func: A function whose argspec will be inspected.
        @type func: A callable.

        @param args: The arguments which could be passed to C{func}.

        @param kw: The keyword arguments which could be passed to C{func}.

        @return: L{_passedSignature} or L{_passedArgSpec}'s return value
        @rtype: L{dict}
        """
        if getattr(inspect, "signature", None):
            # Python 3
            return _passedSignature(inspect.signature(func), args, kw)
        else:
            # Python 2
            return _passedArgSpec(inspect.getargspec(func), args, kw)

    def test_passed_simplePositional(self):
        """
        L{passed} identifies the arguments passed by a simple
        positional test.
        """

        def func(a, b):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2), dict(a=1, b=2))

    def test_passed_tooManyArgs(self):
        """
        L{passed} raises a L{TypeError} if too many arguments are
        passed.
        """

        def func(a, b):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, 2, 3)

    def test_passed_doublePassKeyword(self):
        """
        L{passed} raises a L{TypeError} if a argument is passed both
        positionally and by keyword.
        """

        def func(a):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, a=2)

    def test_passed_unspecifiedKeyword(self):
        """
        L{passed} raises a L{TypeError} if a keyword argument not
        present in the function's declaration is passed.
        """

        def func(a):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, z=2)

    def test_passed_star(self):
        """
        L{passed} places additional positional arguments into a tuple
        under the name of the star argument.
        """

        def func(a, *b):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2, 3), dict(a=1, b=(2, 3)))

    def test_passed_starStar(self):
        """
        Additional keyword arguments are passed as a dict to the star star
        keyword argument.
        """

        def func(a, **b):
            pass

        self.assertEqual(
            self.checkPassed(func, 1, x=2, y=3, z=4), dict(a=1, b=dict(x=2, y=3, z=4))
        )

    def test_passed_noDefaultValues(self):
        """
        The results of L{passed} only include arguments explicitly
        passed, not default values.
        """

        def func(a, b, c=1, d=2, e=3):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2, e=7), dict(a=1, b=2, e=7))

    def test_mutualExclusionPrimeDirective(self):
        """
        L{mutuallyExclusiveArguments} does not interfere in its
        decoratee's operation, either its receipt of arguments or its return
        value.
        """

        @_mutuallyExclusiveArguments([("a", "b")])
        def func(x, y, a=3, b=4):
            return x + y + a + b

        self.assertEqual(func(1, 2), 10)
        self.assertEqual(func(1, 2, 7), 14)
        self.assertEqual(func(1, 2, b=7), 13)

    def test_mutualExclusionExcludesByKeyword(self):
        """
        L{mutuallyExclusiveArguments} raises a L{TypeError}n if its
        decoratee is passed a pair of mutually exclusive arguments.
        """

        @_mutuallyExclusiveArguments([["a", "b"]])
        def func(a=3, b=4):
            return a + b

        self.assertRaises(TypeError, func, a=3, b=4)

    def test_invalidParameterType(self):
        """
        Create a fake signature with an invalid parameter
        type to test error handling.  The valid parameter
        types are specified in L{inspect.Parameter}.
        """

        class FakeSignature:
            def __init__(self, parameters):
                self.parameters = parameters

        class FakeParameter:
            def __init__(self, name, kind):
                self.name = name
                self.kind = kind

        def func(a, b):
            pass

        func(1, 2)
        parameters = inspect.signature(func).parameters
        dummyParameters = parameters.copy()
        dummyParameters["c"] = FakeParameter("fake", "fake")
        fakeSig = FakeSignature(dummyParameters)
        self.assertRaises(TypeError, _passedSignature, fakeSig, (1, 2), {})


class KeywordOnlyTests(SynchronousTestCase):
    """
    Keyword only arguments (PEP 3102).
    """

    def checkPassed(self, func, *args, **kw):
        """
        Test an invocation of L{passed} with the given function, arguments, and
        keyword arguments.

        @param func: A function whose argspec to pass to L{_passed}.
        @type func: A callable.

        @param args: The arguments which could be passed to L{func}.

        @param kw: The keyword arguments which could be passed to L{func}.

        @return: L{_passed}'s return value
        @rtype: L{dict}
        """
        return _passedSignature(inspect.signature(func), args, kw)

    def test_passedKeywordOnly(self):
        """
        Keyword only arguments follow varargs.
        They are specified in PEP 3102.
        """

        def func1(*a, b=True):
            """
            b is a keyword-only argument, with a default value.
            """

        def func2(*a, b=True, c, d, e):
            """
            b, c, d, e  are keyword-only arguments.
            b has a default value.
            """

        self.assertEqual(self.checkPassed(func1, 1, 2, 3), dict(a=(1, 2, 3), b=True))
        self.assertEqual(
            self.checkPassed(func1, 1, 2, 3, b=False), dict(a=(1, 2, 3), b=False)
        )
        self.assertEqual(
            self.checkPassed(func2, 1, 2, b=False, c=1, d=2, e=3),
            dict(a=(1, 2), b=False, c=1, d=2, e=3),
        )
        self.assertRaises(TypeError, self.checkPassed, func2, 1, 2, b=False, c=1, d=2)
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!