python - 如何在运行时覆盖包中的东西?

标签 python python-2.7 oop python-module ietf-netconf

[编辑:我正在运行 Python 2.7.3]

我的职业是网络工程师,我一直在研究 ncclient (网站上的版本很旧,this 是我一直在使用的版本)以使其与 Brocade 的 NETCONF 实现一起使用。为了让它与我们的 Brocade 设备一起工作,我必须进行一些调整,但我不得不 fork 包并对源代码本身进行调整。这对我来说并不“干净”,所以我决定尝试“以正确的方式”去做,并覆盖包中存在的一些东西*;具体三件事:

  1. 名为 build() 的“静态方法”属于 HelloHandler 类,它本身是 SessionListener 的子类
  2. RPC 类的“._id”属性(最初的实现使用 uuid,而 Brocade 盒子不太喜欢这个,所以在我最初的调整中我只是将它更改为一个从未改变的静态值)。
  3. 对构建 XML 过滤器属性的 util 函数进行微调

到目前为止,我在文件 brcd_ncclient.py 中有这段代码:

#!/usr/bin/env python

# hack on XML element creation and create a subclass to override HelloHandler's
# build() method to format the XML in a way that the brocades actually like

from ncclient.xml_ import *
from ncclient.transport.session import HelloHandler
from ncclient.operations.rpc import RPC, RaiseMode
from ncclient.operations import util

# register brocade namespace and create functions to create proper xml for
# hello/capabilities exchange

BROCADE_1_0 = "http://brocade.com/ns/netconf/config/netiron-config/"
register_namespace('brcd', BROCADE_1_0)

brocade_new_ele = lambda tag, ns, attrs={}, **extra: ET.Element(qualify(tag, ns), attrs, **extra)

brocade_sub_ele = lambda parent, tag, ns, attrs={}, **extra: ET.SubElement(parent, qualify(tag, ns), attrs, **extra)

# subclass RPC to override self._id to change uuid-generated message-id's;
# Brocades seem to not be able to handle the really long id's
class BrcdRPC(RPC):
    def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE):
        self._id = "1"
        return super(BrcdRPC, self).self._id

class BrcdHelloHandler(HelloHandler):
    def __init__(self):
        return super(BrcdHelloHandler, self).__init__()

    @staticmethod
    def build(capabilities):
        hello = brocade_new_ele("hello", None, {'xmlns':"urn:ietf:params:xml:ns:netconf:base:1.0"})
        caps = brocade_sub_ele(hello, "capabilities", None)
        def fun(uri): brocade_sub_ele(caps, "capability", None).text = uri
        map(fun, capabilities)
        return to_xml(hello)
        #return super(BrcdHelloHandler, self).build() ???

# since there's no classes I'm assuming I can just override the function itself
# in ncclient.operations.util?
def build_filter(spec, capcheck=None):
    type = None
    if isinstance(spec, tuple):
        type, criteria = spec
        # brocades want the netconf prefix on subtree filter attribute
        rep = new_ele("filter", {'nc:type':type})
        if type == "xpath":
            rep.attrib["select"] = criteria
        elif type == "subtree":
            rep.append(to_ele(criteria))
        else:
            raise OperationError("Invalid filter type")
    else:
        rep = validated_element(spec, ("filter", qualify("filter")),
                                    attrs=("type",))
        # TODO set type var here, check if select attr present in case of xpath..
    if type == "xpath" and capcheck is not None:
        capcheck(":xpath")
    return rep

然后在我的文件 netconftest.py 中我有:

#!/usr/bin/env python

from ncclient import manager
from brcd_ncclient import *

manager.logging.basicConfig(filename='ncclient.log', level=manager.logging.DEBUG)

# brocade server capabilities advertising as 1.1 compliant when they're really not
# this will stop ncclient from attempting 1.1 chunked netconf message transactions
manager.CAPABILITIES = ['urn:ietf:params:netconf:capability:writeable-running:1.0', 'urn:ietf:params:netconf:base:1.0']

# BROCADE_1_0 is the namespace defined for netiron configs in brcd_ncclient
# this maps to the 'brcd' prefix used in xml elements, ie subtree filter criteria
with manager.connect(host='hostname_or_ip', username='username', password='password') as m:
    # 'get' request with no filter - for brocades just shows 'show version' data
    c = m.get()
    print c
    # 'get-config' request with 'mpls-config' filter - if no filter is
    # supplied with 'get-config', brocade returns nothing
    netironcfg = brocade_new_ele('netiron-config', BROCADE_1_0)
    mplsconfig = brocade_sub_ele(netironcfg, 'mpls-config', BROCADE_1_0)
    filterstr = to_xml(netironcfg)
    c2 = m.get_config(source='running', filter=('subtree', filterstr))
    print c2
    # so far it only looks like the supported filters for 'get-config'
    # operations are: 'interface-config', 'vlan-config' and 'mpls-config'

每当我运行我的 netconftest.py 文件时,我都会收到超时错误,因为在日志文件 ncclient.log 中我可以看到我的子类定义(即更改用于 hello 交换的 XML - 静态方法 build) 被忽略,Brocade box 不知道如何解释原始 ncclient HelloHandler.build() 的 XML方法正在生成**。我还可以在生成的日志文件中看到我试图覆盖的其他内容也被忽略了,例如消息 ID(静态值为 1)以及 XML 过滤器。

所以,我有点不知所措。我确实找到了 this blog post/module从我的研究来看,它似乎完全按照我的意愿行事,但我真的很想通过手工来理解我做错了什么,而不是使用某人已经编写的模块不必自己解决这个问题的借口。

*有人可以向我解释一下这是不是“猴子补丁”并且实际上很糟糕?我在研究中发现猴子修补是不可取的,但是 this answerthis answer让我很困惑。对我来说,我希望覆盖这些位将使我不必维护我自己的 ncclient 的整个分支。

**为了提供更多上下文,这个 XML,它是 ncclient.transport.session.HelloHandler.build() 默认生成的,Brocade box 似乎不喜欢:

<?xml version='1.0' encoding='UTF-8'?>
    <nc:hello xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <nc:capabilities>
            <nc:capability>urn:ietf:params:netconf:base:1.0</nc:capability>   
            <nc:capability>urn:ietf:params:netconf:capability:writeable-running:1.0</nc:capability>   
        </nc:capabilities>
    </nc:hello>

我重写的 build() 方法的目的是将上面的 XML 转换成这个(Brocade 喜欢这样:

<?xml version="1.0" encoding="UTF-8"?>
    <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
        <capabilities>
            <capability>urn:ietf:params:netconf:base:1.0</capability>
            <capability>urn:ietf:params:netconf:capability:writeable-running:1.0</capability>
        </capabilities>
    </hello>

最佳答案

所以事实证明,“元信息”不应该被如此仓促地删除,因为同样,当我不完全理解我想问的问题时,很难找到我所追求的答案。我真正想做的是在运行时覆盖包中的内容。

这是我将 brcd_ncclient.py 更改为(为简洁起见删除了注释):

#!/usr/bin/env python

from ncclient import manager
from ncclient.xml_ import *

brcd_new_ele = lambda tag, ns, attrs={}, **extra: ET.Element(qualify(tag, ns), attrs, **extra)
brcd_sub_ele = lambda parent, tag, ns, attrs={}, **extra: ET.SubElement(parent, qualify(tag, ns), attrs, **extra)

BROCADE_1_0 = "http://brocade.com/ns/netconf/config/netiron-config/"
register_namespace('brcd', BROCADE_1_0)

@staticmethod
def brcd_build(capabilities):
    hello = brcd_new_ele("hello", None, {'xmlns':"urn:ietf:params:xml:ns:netconf:base:1.0"})
    caps = brcd_sub_ele(hello, "capabilities", None)
    def fun(uri): brcd_sub_ele(caps, "capability", None).text = uri
    map(fun, capabilities)
    return to_xml(hello)

def brcd_build_filter(spec, capcheck=None):
    type = None
    if isinstance(spec, tuple):
        type, criteria = spec
        # brocades want the netconf prefix on subtree filter attribute
        rep = new_ele("filter", {'nc:type':type})
        if type == "xpath":
            rep.attrib["select"] = criteria
        elif type == "subtree":
            rep.append(to_ele(criteria))
        else:
            raise OperationError("Invalid filter type")
    else:
        rep = validated_element(spec, ("filter", qualify("filter")),
                                attrs=("type",))
    if type == "xpath" and capcheck is not None:
        capcheck(":xpath")
    return rep

manager.transport.session.HelloHandler.build = brcd_build
manager.operations.util.build_filter = brcd_build_filter

然后在 netconftest.py 中:

#!/usr/bin/env python

from brcd_ncclient import *

manager.logging.basicConfig(filename='ncclient.log', level=manager.logging.DEBUG)

manager.CAPABILITIES = ['urn:ietf:params:netconf:capability:writeable-running:1.0', 'urn:ietf:params:netconf:base:1.0']

with manager.connect(host='host', username='user', password='password') as m:
    netironcfg = brcd_new_ele('netiron-config', BROCADE_1_0)
    mplsconfig = brcd_sub_ele(netironcfg, 'mpls-config', BROCADE_1_0)
    filterstr = to_xml(netironcfg)
    c2 = m.get_config(source='running', filter=('subtree', filterstr))
    print c2

这几乎让我到达了我想去的地方。我仍然必须编辑原始源代码以更改使用 uuid1().urn 生成的消息 ID,因为我之前没有弄清楚或不了解如何更改对象的属性__init__ 发生在运行时(先有鸡还是先有蛋?);这是 ncclient/operations/rpc.py 中的违规代码:

class RPC(object):
    DEPENDS = []
    REPLY_CLS = RPCReply
    def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE):
        self._session = session
        try:
            for cap in self.DEPENDS:
                self._assert(cap)
        except AttributeError:
            pass
        self._async = async
        self._timeout = timeout
        self._raise_mode = raise_mode
        self._id = uuid1().urn # Keeps things simple instead of having a class attr with running ID that has to be locked

归功于 this recipe on ActiveState终于让我了解了我真正想做的事情。我不认为我最初发布的代码在技术上是不正确的 - 如果我想做的是 fork 我自己的 ncclient 并对其进行更改和/或维护它,那根本不是我想做的,至少现在不是。

我将编辑我的问题标题以更好地反射(reflect)我最初想要的 - 如果其他人有更好或更清晰的想法,我完全开放。

关于python - 如何在运行时覆盖包中的东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14177305/

相关文章:

python - 用于删除具有重复整数的数字的函数会跳过一项,我不知道为什么

Java OOP 改变二维矩阵的位置?

javascript - jQuery 和面向对象的 JavaScript - 怎么样?

java - 原型(prototype)设计模式和简单的 Java 克隆之间的区别

python - 计算从 4 个 mysql 表中检索到的所有可能文本对的余弦相似度

python - Python 中的一小时差异

python - 交替应用于 Pandas

python - python中的函数调用和类型转换?

python - Gtk 3 python 入口颜色

python - 从 Python Tkinter 中的特定位置播放声音