OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. |
| 3 # Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved |
| 4 # |
| 5 # Permission is hereby granted, free of charge, to any person obtaining a |
| 6 # copy of this software and associated documentation files (the |
| 7 # "Software"), to deal in the Software without restriction, including |
| 8 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 9 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 10 # persons to whom the Software is furnished to do so, subject to the fol- |
| 11 # lowing conditions: |
| 12 # |
| 13 # The above copyright notice and this permission notice shall be included |
| 14 # in all copies or substantial portions of the Software. |
| 15 # |
| 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 22 # IN THE SOFTWARE. |
| 23 |
| 24 """ |
| 25 Represents an EC2 Instance |
| 26 """ |
| 27 import boto |
| 28 from boto.ec2.ec2object import EC2Object, TaggedEC2Object |
| 29 from boto.resultset import ResultSet |
| 30 from boto.ec2.address import Address |
| 31 from boto.ec2.blockdevicemapping import BlockDeviceMapping |
| 32 from boto.ec2.image import ProductCodes |
| 33 from boto.ec2.networkinterface import NetworkInterface |
| 34 from boto.ec2.group import Group |
| 35 import base64 |
| 36 |
| 37 |
| 38 class InstanceState(object): |
| 39 """ |
| 40 The state of the instance. |
| 41 |
| 42 :ivar code: The low byte represents the state. The high byte is an |
| 43 opaque internal value and should be ignored. Valid values: |
| 44 |
| 45 * 0 (pending) |
| 46 * 16 (running) |
| 47 * 32 (shutting-down) |
| 48 * 48 (terminated) |
| 49 * 64 (stopping) |
| 50 * 80 (stopped) |
| 51 |
| 52 :ivar name: The name of the state of the instance. Valid values: |
| 53 |
| 54 * "pending" |
| 55 * "running" |
| 56 * "shutting-down" |
| 57 * "terminated" |
| 58 * "stopping" |
| 59 * "stopped" |
| 60 """ |
| 61 def __init__(self, code=0, name=None): |
| 62 self.code = code |
| 63 self.name = name |
| 64 |
| 65 def __repr__(self): |
| 66 return '%s(%d)' % (self.name, self.code) |
| 67 |
| 68 def startElement(self, name, attrs, connection): |
| 69 pass |
| 70 |
| 71 def endElement(self, name, value, connection): |
| 72 if name == 'code': |
| 73 self.code = int(value) |
| 74 elif name == 'name': |
| 75 self.name = value |
| 76 else: |
| 77 setattr(self, name, value) |
| 78 |
| 79 |
| 80 class InstancePlacement(object): |
| 81 """ |
| 82 The location where the instance launched. |
| 83 |
| 84 :ivar zone: The Availability Zone of the instance. |
| 85 :ivar group_name: The name of the placement group the instance is |
| 86 in (for cluster compute instances). |
| 87 :ivar tenancy: The tenancy of the instance (if the instance is |
| 88 running within a VPC). An instance with a tenancy of dedicated |
| 89 runs on single-tenant hardware. |
| 90 """ |
| 91 def __init__(self, zone=None, group_name=None, tenancy=None): |
| 92 self.zone = zone |
| 93 self.group_name = group_name |
| 94 self.tenancy = tenancy |
| 95 |
| 96 def __repr__(self): |
| 97 return self.zone |
| 98 |
| 99 def startElement(self, name, attrs, connection): |
| 100 pass |
| 101 |
| 102 def endElement(self, name, value, connection): |
| 103 if name == 'availabilityZone': |
| 104 self.zone = value |
| 105 elif name == 'groupName': |
| 106 self.group_name = value |
| 107 elif name == 'tenancy': |
| 108 self.tenancy = value |
| 109 else: |
| 110 setattr(self, name, value) |
| 111 |
| 112 |
| 113 class Reservation(EC2Object): |
| 114 """ |
| 115 Represents a Reservation response object. |
| 116 |
| 117 :ivar id: The unique ID of the Reservation. |
| 118 :ivar owner_id: The unique ID of the owner of the Reservation. |
| 119 :ivar groups: A list of Group objects representing the security |
| 120 groups associated with launched instances. |
| 121 :ivar instances: A list of Instance objects launched in this |
| 122 Reservation. |
| 123 """ |
| 124 def __init__(self, connection=None): |
| 125 EC2Object.__init__(self, connection) |
| 126 self.id = None |
| 127 self.owner_id = None |
| 128 self.groups = [] |
| 129 self.instances = [] |
| 130 |
| 131 def __repr__(self): |
| 132 return 'Reservation:%s' % self.id |
| 133 |
| 134 def startElement(self, name, attrs, connection): |
| 135 if name == 'instancesSet': |
| 136 self.instances = ResultSet([('item', Instance)]) |
| 137 return self.instances |
| 138 elif name == 'groupSet': |
| 139 self.groups = ResultSet([('item', Group)]) |
| 140 return self.groups |
| 141 else: |
| 142 return None |
| 143 |
| 144 def endElement(self, name, value, connection): |
| 145 if name == 'reservationId': |
| 146 self.id = value |
| 147 elif name == 'ownerId': |
| 148 self.owner_id = value |
| 149 else: |
| 150 setattr(self, name, value) |
| 151 |
| 152 def stop_all(self): |
| 153 for instance in self.instances: |
| 154 instance.stop() |
| 155 |
| 156 |
| 157 class Instance(TaggedEC2Object): |
| 158 """ |
| 159 Represents an instance. |
| 160 |
| 161 :ivar id: The unique ID of the Instance. |
| 162 :ivar groups: A list of Group objects representing the security |
| 163 groups associated with the instance. |
| 164 :ivar public_dns_name: The public dns name of the instance. |
| 165 :ivar private_dns_name: The private dns name of the instance. |
| 166 :ivar state: The string representation of the instance's current state. |
| 167 :ivar state_code: An integer representation of the instance's |
| 168 current state. |
| 169 :ivar previous_state: The string representation of the instance's |
| 170 previous state. |
| 171 :ivar previous_state_code: An integer representation of the |
| 172 instance's current state. |
| 173 :ivar key_name: The name of the SSH key associated with the instance. |
| 174 :ivar instance_type: The type of instance (e.g. m1.small). |
| 175 :ivar launch_time: The time the instance was launched. |
| 176 :ivar image_id: The ID of the AMI used to launch this instance. |
| 177 :ivar placement: The availability zone in which the instance is running. |
| 178 :ivar placement_group: The name of the placement group the instance |
| 179 is in (for cluster compute instances). |
| 180 :ivar placement_tenancy: The tenancy of the instance, if the instance |
| 181 is running within a VPC. An instance with a tenancy of dedicated |
| 182 runs on a single-tenant hardware. |
| 183 :ivar kernel: The kernel associated with the instance. |
| 184 :ivar ramdisk: The ramdisk associated with the instance. |
| 185 :ivar architecture: The architecture of the image (i386|x86_64). |
| 186 :ivar hypervisor: The hypervisor used. |
| 187 :ivar virtualization_type: The type of virtualization used. |
| 188 :ivar product_codes: A list of product codes associated with this instance. |
| 189 :ivar ami_launch_index: This instances position within it's launch group. |
| 190 :ivar monitored: A boolean indicating whether monitoring is enabled or not. |
| 191 :ivar spot_instance_request_id: The ID of the spot instance request |
| 192 if this is a spot instance. |
| 193 :ivar subnet_id: The VPC Subnet ID, if running in VPC. |
| 194 :ivar vpc_id: The VPC ID, if running in VPC. |
| 195 :ivar private_ip_address: The private IP address of the instance. |
| 196 :ivar ip_address: The public IP address of the instance. |
| 197 :ivar platform: Platform of the instance (e.g. Windows) |
| 198 :ivar root_device_name: The name of the root device. |
| 199 :ivar root_device_type: The root device type (ebs|instance-store). |
| 200 :ivar block_device_mapping: The Block Device Mapping for the instance. |
| 201 :ivar state_reason: The reason for the most recent state transition. |
| 202 :ivar groups: List of security Groups associated with the instance. |
| 203 :ivar interfaces: List of Elastic Network Interfaces associated with |
| 204 this instance. |
| 205 :ivar ebs_optimized: Whether instance is using optimized EBS volumes |
| 206 or not. |
| 207 :ivar instance_profile: A Python dict containing the instance |
| 208 profile id and arn associated with this instance. |
| 209 """ |
| 210 |
| 211 def __init__(self, connection=None): |
| 212 TaggedEC2Object.__init__(self, connection) |
| 213 self.id = None |
| 214 self.dns_name = None |
| 215 self.public_dns_name = None |
| 216 self.private_dns_name = None |
| 217 self.key_name = None |
| 218 self.instance_type = None |
| 219 self.launch_time = None |
| 220 self.image_id = None |
| 221 self.kernel = None |
| 222 self.ramdisk = None |
| 223 self.product_codes = ProductCodes() |
| 224 self.ami_launch_index = None |
| 225 self.monitored = False |
| 226 self.spot_instance_request_id = None |
| 227 self.subnet_id = None |
| 228 self.vpc_id = None |
| 229 self.private_ip_address = None |
| 230 self.ip_address = None |
| 231 self.requester_id = None |
| 232 self._in_monitoring_element = False |
| 233 self.persistent = False |
| 234 self.root_device_name = None |
| 235 self.root_device_type = None |
| 236 self.block_device_mapping = None |
| 237 self.state_reason = None |
| 238 self.group_name = None |
| 239 self.client_token = None |
| 240 self.eventsSet = None |
| 241 self.groups = [] |
| 242 self.platform = None |
| 243 self.interfaces = [] |
| 244 self.hypervisor = None |
| 245 self.virtualization_type = None |
| 246 self.architecture = None |
| 247 self.instance_profile = None |
| 248 self._previous_state = None |
| 249 self._state = InstanceState() |
| 250 self._placement = InstancePlacement() |
| 251 |
| 252 def __repr__(self): |
| 253 return 'Instance:%s' % self.id |
| 254 |
| 255 @property |
| 256 def state(self): |
| 257 return self._state.name |
| 258 |
| 259 @property |
| 260 def state_code(self): |
| 261 return self._state.code |
| 262 |
| 263 @property |
| 264 def previous_state(self): |
| 265 if self._previous_state: |
| 266 return self._previous_state.name |
| 267 return None |
| 268 |
| 269 @property |
| 270 def previous_state_code(self): |
| 271 if self._previous_state: |
| 272 return self._previous_state.code |
| 273 return 0 |
| 274 |
| 275 @property |
| 276 def state(self): |
| 277 return self._state.name |
| 278 |
| 279 @property |
| 280 def placement(self): |
| 281 return self._placement.zone |
| 282 |
| 283 @property |
| 284 def placement_group(self): |
| 285 return self._placement.group_name |
| 286 |
| 287 @property |
| 288 def placement_tenancy(self): |
| 289 return self._placement.tenancy |
| 290 |
| 291 def startElement(self, name, attrs, connection): |
| 292 retval = TaggedEC2Object.startElement(self, name, attrs, connection) |
| 293 if retval is not None: |
| 294 return retval |
| 295 if name == 'monitoring': |
| 296 self._in_monitoring_element = True |
| 297 elif name == 'blockDeviceMapping': |
| 298 self.block_device_mapping = BlockDeviceMapping() |
| 299 return self.block_device_mapping |
| 300 elif name == 'productCodes': |
| 301 return self.product_codes |
| 302 elif name == 'stateReason': |
| 303 self.state_reason = SubParse('stateReason') |
| 304 return self.state_reason |
| 305 elif name == 'groupSet': |
| 306 self.groups = ResultSet([('item', Group)]) |
| 307 return self.groups |
| 308 elif name == "eventsSet": |
| 309 self.eventsSet = SubParse('eventsSet') |
| 310 return self.eventsSet |
| 311 elif name == 'networkInterfaceSet': |
| 312 self.interfaces = ResultSet([('item', NetworkInterface)]) |
| 313 elif name == 'iamInstanceProfile': |
| 314 self.instance_profile = SubParse('iamInstanceProfile') |
| 315 return self.instance_profile |
| 316 elif name == 'currentState': |
| 317 return self._state |
| 318 elif name == 'previousState': |
| 319 self._previous_state = InstanceState() |
| 320 return self._previous_state |
| 321 elif name == 'instanceState': |
| 322 return self._state |
| 323 elif name == 'placement': |
| 324 return self._placement |
| 325 return None |
| 326 |
| 327 def endElement(self, name, value, connection): |
| 328 if name == 'instanceId': |
| 329 self.id = value |
| 330 elif name == 'imageId': |
| 331 self.image_id = value |
| 332 elif name == 'dnsName' or name == 'publicDnsName': |
| 333 self.dns_name = value # backwards compatibility |
| 334 self.public_dns_name = value |
| 335 elif name == 'privateDnsName': |
| 336 self.private_dns_name = value |
| 337 elif name == 'keyName': |
| 338 self.key_name = value |
| 339 elif name == 'amiLaunchIndex': |
| 340 self.ami_launch_index = value |
| 341 elif name == 'previousState': |
| 342 self.previous_state = value |
| 343 elif name == 'name': |
| 344 self.state = value |
| 345 elif name == 'code': |
| 346 try: |
| 347 self.state_code = int(value) |
| 348 except ValueError: |
| 349 boto.log.warning('Error converting code (%s) to int' % value) |
| 350 self.state_code = value |
| 351 elif name == 'instanceType': |
| 352 self.instance_type = value |
| 353 elif name == 'rootDeviceName': |
| 354 self.root_device_name = value |
| 355 elif name == 'rootDeviceType': |
| 356 self.root_device_type = value |
| 357 elif name == 'launchTime': |
| 358 self.launch_time = value |
| 359 elif name == 'platform': |
| 360 self.platform = value |
| 361 elif name == 'kernelId': |
| 362 self.kernel = value |
| 363 elif name == 'ramdiskId': |
| 364 self.ramdisk = value |
| 365 elif name == 'state': |
| 366 if self._in_monitoring_element: |
| 367 if value == 'enabled': |
| 368 self.monitored = True |
| 369 self._in_monitoring_element = False |
| 370 elif name == 'spotInstanceRequestId': |
| 371 self.spot_instance_request_id = value |
| 372 elif name == 'subnetId': |
| 373 self.subnet_id = value |
| 374 elif name == 'vpcId': |
| 375 self.vpc_id = value |
| 376 elif name == 'privateIpAddress': |
| 377 self.private_ip_address = value |
| 378 elif name == 'ipAddress': |
| 379 self.ip_address = value |
| 380 elif name == 'requesterId': |
| 381 self.requester_id = value |
| 382 elif name == 'persistent': |
| 383 if value == 'true': |
| 384 self.persistent = True |
| 385 else: |
| 386 self.persistent = False |
| 387 elif name == 'groupName': |
| 388 if self._in_monitoring_element: |
| 389 self.group_name = value |
| 390 elif name == 'clientToken': |
| 391 self.client_token = value |
| 392 elif name == "eventsSet": |
| 393 self.events = value |
| 394 elif name == 'hypervisor': |
| 395 self.hypervisor = value |
| 396 elif name == 'virtualizationType': |
| 397 self.virtualization_type = value |
| 398 elif name == 'architecture': |
| 399 self.architecture = value |
| 400 elif name == 'ebsOptimized': |
| 401 self.ebs_optimized = (value == 'true') |
| 402 else: |
| 403 setattr(self, name, value) |
| 404 |
| 405 def _update(self, updated): |
| 406 self.__dict__.update(updated.__dict__) |
| 407 |
| 408 def update(self, validate=False): |
| 409 """ |
| 410 Update the instance's state information by making a call to fetch |
| 411 the current instance attributes from the service. |
| 412 |
| 413 :type validate: bool |
| 414 :param validate: By default, if EC2 returns no data about the |
| 415 instance the update method returns quietly. If |
| 416 the validate param is True, however, it will |
| 417 raise a ValueError exception if no data is |
| 418 returned from EC2. |
| 419 """ |
| 420 rs = self.connection.get_all_instances([self.id]) |
| 421 if len(rs) > 0: |
| 422 r = rs[0] |
| 423 for i in r.instances: |
| 424 if i.id == self.id: |
| 425 self._update(i) |
| 426 elif validate: |
| 427 raise ValueError('%s is not a valid Instance ID' % self.id) |
| 428 return self.state |
| 429 |
| 430 def terminate(self): |
| 431 """ |
| 432 Terminate the instance |
| 433 """ |
| 434 rs = self.connection.terminate_instances([self.id]) |
| 435 if len(rs) > 0: |
| 436 self._update(rs[0]) |
| 437 |
| 438 def stop(self, force=False): |
| 439 """ |
| 440 Stop the instance |
| 441 |
| 442 :type force: bool |
| 443 :param force: Forces the instance to stop |
| 444 |
| 445 :rtype: list |
| 446 :return: A list of the instances stopped |
| 447 """ |
| 448 rs = self.connection.stop_instances([self.id], force) |
| 449 if len(rs) > 0: |
| 450 self._update(rs[0]) |
| 451 |
| 452 def start(self): |
| 453 """ |
| 454 Start the instance. |
| 455 """ |
| 456 rs = self.connection.start_instances([self.id]) |
| 457 if len(rs) > 0: |
| 458 self._update(rs[0]) |
| 459 |
| 460 def reboot(self): |
| 461 return self.connection.reboot_instances([self.id]) |
| 462 |
| 463 def get_console_output(self): |
| 464 """ |
| 465 Retrieves the console output for the instance. |
| 466 |
| 467 :rtype: :class:`boto.ec2.instance.ConsoleOutput` |
| 468 :return: The console output as a ConsoleOutput object |
| 469 """ |
| 470 return self.connection.get_console_output(self.id) |
| 471 |
| 472 def confirm_product(self, product_code): |
| 473 return self.connection.confirm_product_instance(self.id, product_code) |
| 474 |
| 475 def use_ip(self, ip_address): |
| 476 if isinstance(ip_address, Address): |
| 477 ip_address = ip_address.public_ip |
| 478 return self.connection.associate_address(self.id, ip_address) |
| 479 |
| 480 def monitor(self): |
| 481 return self.connection.monitor_instance(self.id) |
| 482 |
| 483 def unmonitor(self): |
| 484 return self.connection.unmonitor_instance(self.id) |
| 485 |
| 486 def get_attribute(self, attribute): |
| 487 """ |
| 488 Gets an attribute from this instance. |
| 489 |
| 490 :type attribute: string |
| 491 :param attribute: The attribute you need information about |
| 492 Valid choices are: |
| 493 |
| 494 * instanceType |
| 495 * kernel |
| 496 * ramdisk |
| 497 * userData |
| 498 * disableApiTermination |
| 499 * instanceInitiatedShutdownBehavior |
| 500 * rootDeviceName |
| 501 * blockDeviceMapping |
| 502 * productCodes |
| 503 * sourceDestCheck |
| 504 * groupSet |
| 505 * ebsOptimized |
| 506 |
| 507 :rtype: :class:`boto.ec2.image.InstanceAttribute` |
| 508 :return: An InstanceAttribute object representing the value of the |
| 509 attribute requested |
| 510 """ |
| 511 return self.connection.get_instance_attribute(self.id, attribute) |
| 512 |
| 513 def modify_attribute(self, attribute, value): |
| 514 """ |
| 515 Changes an attribute of this instance |
| 516 |
| 517 :type attribute: string |
| 518 :param attribute: The attribute you wish to change. |
| 519 |
| 520 * instanceType - A valid instance type (m1.small) |
| 521 * kernel - Kernel ID (None) |
| 522 * ramdisk - Ramdisk ID (None) |
| 523 * userData - Base64 encoded String (None) |
| 524 * disableApiTermination - Boolean (true) |
| 525 * instanceInitiatedShutdownBehavior - stop|terminate |
| 526 * sourceDestCheck - Boolean (true) |
| 527 * groupSet - Set of Security Groups or IDs |
| 528 * ebsOptimized - Boolean (false) |
| 529 |
| 530 :type value: string |
| 531 :param value: The new value for the attribute |
| 532 |
| 533 :rtype: bool |
| 534 :return: Whether the operation succeeded or not |
| 535 """ |
| 536 return self.connection.modify_instance_attribute(self.id, attribute, |
| 537 value) |
| 538 |
| 539 def reset_attribute(self, attribute): |
| 540 """ |
| 541 Resets an attribute of this instance to its default value. |
| 542 |
| 543 :type attribute: string |
| 544 :param attribute: The attribute to reset. Valid values are: |
| 545 kernel|ramdisk |
| 546 |
| 547 :rtype: bool |
| 548 :return: Whether the operation succeeded or not |
| 549 """ |
| 550 return self.connection.reset_instance_attribute(self.id, attribute) |
| 551 |
| 552 def create_image( |
| 553 self, name, |
| 554 description=None, no_reboot=False |
| 555 ): |
| 556 """ |
| 557 Will create an AMI from the instance in the running or stopped |
| 558 state. |
| 559 |
| 560 :type name: string |
| 561 :param name: The name of the new image |
| 562 |
| 563 :type description: string |
| 564 :param description: An optional human-readable string describing |
| 565 the contents and purpose of the AMI. |
| 566 |
| 567 :type no_reboot: bool |
| 568 :param no_reboot: An optional flag indicating that the bundling process |
| 569 should not attempt to shutdown the instance before |
| 570 bundling. If this flag is True, the responsibility |
| 571 of maintaining file system integrity is left to the |
| 572 owner of the instance. |
| 573 |
| 574 :rtype: string |
| 575 :return: The new image id |
| 576 """ |
| 577 return self.connection.create_image(self.id, name, description, no_reboo
t) |
| 578 |
| 579 |
| 580 class ConsoleOutput: |
| 581 |
| 582 def __init__(self, parent=None): |
| 583 self.parent = parent |
| 584 self.instance_id = None |
| 585 self.timestamp = None |
| 586 self.output = None |
| 587 |
| 588 def startElement(self, name, attrs, connection): |
| 589 return None |
| 590 |
| 591 def endElement(self, name, value, connection): |
| 592 if name == 'instanceId': |
| 593 self.instance_id = value |
| 594 elif name == 'timestamp': |
| 595 self.timestamp = value |
| 596 elif name == 'output': |
| 597 self.output = base64.b64decode(value) |
| 598 else: |
| 599 setattr(self, name, value) |
| 600 |
| 601 |
| 602 class InstanceAttribute(dict): |
| 603 |
| 604 ValidValues = ['instanceType', 'kernel', 'ramdisk', 'userData', |
| 605 'disableApiTermination', |
| 606 'instanceInitiatedShutdownBehavior', |
| 607 'rootDeviceName', 'blockDeviceMapping', 'sourceDestCheck', |
| 608 'groupSet'] |
| 609 |
| 610 def __init__(self, parent=None): |
| 611 dict.__init__(self) |
| 612 self.instance_id = None |
| 613 self.request_id = None |
| 614 self._current_value = None |
| 615 |
| 616 def startElement(self, name, attrs, connection): |
| 617 if name == 'blockDeviceMapping': |
| 618 self[name] = BlockDeviceMapping() |
| 619 return self[name] |
| 620 elif name == 'groupSet': |
| 621 self[name] = ResultSet([('item', Group)]) |
| 622 return self[name] |
| 623 else: |
| 624 return None |
| 625 |
| 626 def endElement(self, name, value, connection): |
| 627 if name == 'instanceId': |
| 628 self.instance_id = value |
| 629 elif name == 'requestId': |
| 630 self.request_id = value |
| 631 elif name == 'value': |
| 632 if value == 'true': |
| 633 value = True |
| 634 elif value == 'false': |
| 635 value = False |
| 636 self._current_value = value |
| 637 elif name in self.ValidValues: |
| 638 self[name] = self._current_value |
| 639 |
| 640 |
| 641 class SubParse(dict): |
| 642 |
| 643 def __init__(self, section, parent=None): |
| 644 dict.__init__(self) |
| 645 self.section = section |
| 646 |
| 647 def startElement(self, name, attrs, connection): |
| 648 return None |
| 649 |
| 650 def endElement(self, name, value, connection): |
| 651 if name != self.section: |
| 652 self[name] = value |
OLD | NEW |