@@ -737,8 +737,121 @@ service = container.get_object("MovieLister")
737737</objects>
738738]]> </programlisting >
739739
740- </section >
740+ <section id =" objects-config-xmlconfig-inheritance" >
741+ <title >Object definition inheritance</title >
742+
743+ <para >
744+ XMLConfig's definitions may be stacked up into hierarchies of abstract
745+ parents and their children objects. A child object not
746+ only inherits all the properties and constructor arguments from
747+ its parent but it can also easily override any of the inherited
748+ values. This can save a lot of typing when configuring
749+ non-trivial application contexts which would otherwise need to
750+ repeat the same configuration properties over many objects
751+ definitions.
752+ </para >
753+ <para >
754+ An abstract object is identified by having an
755+ <emphasis >abstract="True"</emphasis > attribute and the child
756+ ones are those which have a <emphasis >parent</emphasis > attribute
757+ set to ID of an object from which the properties or constructor
758+ arguments should be inherited. Child objects must not specify
759+ the <emphasis >class</emphasis > attribute, its value is taken
760+ from their parents.
761+ </para >
762+ <para >
763+ An object may be both a child and an abstract one.
764+ </para >
765+
766+ <para >
767+ Here's a hypothetical configuration of a set of services exposed
768+ by a server. Note how you can easily change the CRM environment
769+ you're invoking by merely changing the concrete service's
770+ (get_customer_id or get_customer_profile) parent ID.
771+ </para >
772+
773+ <programlisting ><![CDATA[
774+ <?xml version="1.0" encoding="UTF-8"?>
775+ <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1"
776+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
777+ xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1
778+ http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd">
779+
780+ <object id="service" class="springpythontest.support.testSupportClasses.Service" scope="singleton" abstract="True" lazy-init="True">
781+ <property name="ip"><value>192.168.1.153</value></property>
782+ </object>
783+
784+ <object id="crm_service_dev" parent="service" abstract="True">
785+ <property name="port"><value>3392</value></property>
786+ </object>
787+
788+ <object id="crm_service_test" parent="service" abstract="True">
789+ <property name="port"><value>3393</value></property>
790+ </object>
791+
792+ <object id="get_customer_id" parent="crm_service_dev">
793+ <property name="path"><value>/soap/invoke/get_customer_id</value></property>
794+ </object>
795+
796+ <object id="get_customer_profile" parent="crm_service_test">
797+ <property name="path"><value>/soap/invoke/get_customer_profile</value></property>
798+ </object>
799+
800+ </objects>
801+ ]]> </programlisting >
802+
803+ <para >
804+ Here's how you can override inherited properties; both get_customer_id
805+ and get_customer_profile object definitions will inherit the
806+ <emphasis >path</emphasis > property however the actual objects returned by
807+ the container will use local, overridden, values of the property.
808+ </para >
809+
810+ <programlisting ><![CDATA[
811+ <?xml version="1.0" encoding="UTF-8"?>
812+ <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1"
813+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
814+ xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1
815+ http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd">
816+
817+ <object id="service" class="springpythontest.support.testSupportClasses.Service" scope="singleton" abstract="True" lazy-init="True">
818+ <property name="ip"><value>192.168.1.153</value></property>
819+ <property name="port"><value>3392</value></property>
820+ <property name="path"><value>/DOES-NOT-EXIST</value></property>
821+ </object>
822+
823+ <object id="get_customer_id" parent="service">
824+ <property name="path"><value>/soap/invoke/get_customer_id</value></property>
825+ </object>
826+
827+ <object id="get_customer_profile" parent="service">
828+ <property name="path"><value>/soap/invoke/get_customer_profile</value></property>
829+ </object>
830+
831+ </objects>
832+ ]]> </programlisting >
833+
834+ <para >
835+ If you need to get an abstract object from a container, use the .get_object's <emphasis >ignore_abstract</emphasis > parameter,
836+ otherwise <emphasis >springpython.container.AbstractObjectException</emphasis > will be raised. Observe the difference:
837+ </para >
838+
839+ <programlisting ><![CDATA[
840+ # .. skip creating the context
841+
842+ # No exception will be raised, even though 'service' is an abstract object
843+ service = ctx.get_object("service", ignore_abstract=True)
844+
845+ # Will show the object
846+ print service
741847
848+ # Will raise AbstractObjectException
849+ service = ctx.get_object("service")
850+ ]]> </programlisting >
851+ </section >
852+
853+ </section >
854+
742855 <section id =" objects-config-yamlconfig" >
743856 <title ><classname >YamlConfig</classname > - Spring Python's YAML format</title >
744857
@@ -1199,6 +1312,129 @@ constructor-args:
11991312
12001313 </section >
12011314
1315+ <section id =" objects-config-yamlconfig-inheritance" >
1316+ <title >Object definition inheritance</title >
1317+
1318+ <para >
1319+ Just like XMLConfig, YamlConfig allows for wiring the objects
1320+ definitions into hierarchies of abstract and children objects,
1321+ thus this section is in most parts a repetition of what's
1322+ documented
1323+ <link linkend =" objects-config-xmlconfig-inheritance" >here</link >
1324+ </para >
1325+
1326+ <para >
1327+ Definitions may be stacked up into hierarchies of abstract
1328+ parents and their children objects. A child object not
1329+ only inherits all the properties and constructor arguments from
1330+ its parent but it can also easily override any of the inherited
1331+ values. This can save a lot of typing when configuring
1332+ non-trivial application contexts which would otherwise need to
1333+ repeat the same configuration properties over many objects
1334+ definitions.
1335+ </para >
1336+ <para >
1337+ An abstract object is identified by having an
1338+ <emphasis >abstract</emphasis > attribute equal to True and the child
1339+ ones are those which have a <emphasis >parent</emphasis > attribute
1340+ set to ID of an object from which the properties or constructor
1341+ arguments should be inherited. Child objects must not specify
1342+ the <emphasis >class</emphasis > attribute, its value is taken
1343+ from their parents.
1344+ </para >
1345+ <para >
1346+ An object may be both a child and an abstract one.
1347+ </para >
1348+
1349+ <para >
1350+ Here's a hypothetical configuration of a set of services exposed
1351+ by a server. Note how you can easily change the CRM environment
1352+ you're invoking by merely changing the concrete service's
1353+ (get_customer_id or get_customer_profile) parent ID.
1354+ </para >
1355+
1356+ <programlisting ><![CDATA[
1357+ objects:
1358+ - object: service
1359+ class: springpythontest.support.testSupportClasses.Service
1360+ abstract: True
1361+ scope: singleton
1362+ lazy-init: True
1363+ properties:
1364+ ip: 192.168.1.153
1365+
1366+ - object: crm_service_dev
1367+ abstract: True
1368+ parent: service
1369+ properties:
1370+ port: "3392"
1371+
1372+ - object: crm_service_test
1373+ abstract: True
1374+ parent: service
1375+ properties:
1376+ port: "3393"
1377+
1378+ - object: get_customer_id
1379+ parent: crm_service_dev
1380+ properties:
1381+ path: /soap/invoke/get_customer_id
1382+
1383+ - object: get_customer_profile
1384+ parent: crm_service_test
1385+ properties:
1386+ path: /soap/invoke/get_customer_profile
1387+ ]]> </programlisting >
1388+
1389+ <para >
1390+ Here's how you can override inherited properties; both get_customer_id
1391+ and get_customer_profile object definitions will inherit the
1392+ <emphasis >path</emphasis > property however the actual objects returned by
1393+ the container will use local, overridden, values of the property.
1394+ </para >
1395+
1396+ <programlisting ><![CDATA[
1397+ objects:
1398+ - object: service
1399+ class: foo.Service
1400+ abstract: True
1401+ scope: singleton
1402+ lazy-init: True
1403+ properties:
1404+ ip: 192.168.1.153
1405+ port: "3392"
1406+ path: /DOES-NOT-EXIST
1407+
1408+ - object: get_customer_id
1409+ parent: service
1410+ properties:
1411+ path: /soap/invoke/get_customer_id
1412+
1413+ - object: get_customer_profile
1414+ parent: service
1415+ properties:
1416+ path: /soap/invoke/get_customer_profile
1417+ ]]> </programlisting >
1418+
1419+ <para >
1420+ If you need to get an abstract object from a container, use the .get_object's <emphasis >ignore_abstract</emphasis > parameter,
1421+ otherwise <emphasis >springpython.container.AbstractObjectException</emphasis > will be raised. Observe the difference:
1422+ </para >
1423+
1424+ <programlisting ><![CDATA[
1425+ # .. skip creating the context
1426+
1427+ # No exception will be raised, even though 'service' is an abstract object
1428+ service = ctx.get_object("service", ignore_abstract=True)
1429+
1430+ # Will show the object
1431+ print service
1432+
1433+ # Will raise AbstractObjectException
1434+ service = ctx.get_object("service")
1435+ ]]> </programlisting >
1436+ </section >
1437+
12021438 </section >
12031439
12041440 <section id =" objects-config-object" >
@@ -1250,6 +1486,104 @@ from springpython.context import ApplicationContext
12501486
12511487container = ApplicationContext(MovieBasedApplicationContext())
12521488service = container.get_object("MovieLister")
1489+ ]]> </programlisting >
1490+
1491+ <para >PythonConfig's support for abstract objects is different to
1492+ that of
1493+ <link linkend =" objects-config-xmlconfig-inheritance" >XMLConfig</link >
1494+ or
1495+ <link linkend =" objects-config-yamlconfig-inheritance" >YamlConfig</link >.
1496+ With PythonConfig, the children object do not automatically
1497+ inherit nor override the parents' properties, they in fact
1498+ <emphasis >receive</emphasis > the values returned by their
1499+ parents and it's up to them to decide, using Python code,
1500+ whether to use or to discard the values received.
1501+ </para >
1502+
1503+ <para >
1504+ A child object must have as many optional arguments as there
1505+ are expected to be returned by its parent.
1506+ </para >
1507+
1508+ <para >
1509+ Observe that in the following example the child definitions must
1510+ define an optional 'req' argument; in runtime they will be passed
1511+ its value basing on what their parent object will return.
1512+ Note also that <emphasis >request</emphasis > is of PROTOTYPE
1513+ scope, if it were a SINGLETON then both get_customer_id_request
1514+ and get_customer_profile_request would receive the very same
1515+ Request instance which, in other circumstances, could be a
1516+ desirable effect but not in the example.
1517+ </para >
1518+
1519+ <programlisting ><![CDATA[
1520+
1521+ # stdlib
1522+ import uuid4
1523+
1524+ # .. skip Spring Python imports
1525+
1526+ class Request(object):
1527+ def __init__(self, nonce=None, user=None, password=None):
1528+ self.nonce = nonce
1529+ self.user = user
1530+ self.password = password
1531+
1532+ def __str__(self):
1533+ return "<id=%s %s %s %s>" % (hex(id(self)), self.nonce, self.user, self.password)
1534+
1535+ class TestAbstractContext(PythonConfig):
1536+
1537+ @Object(scope.PROTOTYPE, abstract=True)
1538+ def request(self):
1539+ return Request()
1540+
1541+ @Object(parent="request")
1542+ def request_dev(self, req=None):
1543+ req.user = "dev-user"
1544+ req.password = "dev-password"
1545+
1546+ return req
1547+
1548+ @Object(parent="request")
1549+ def request_test(self, req=None):
1550+ req.user = "test-user"
1551+ req.password = "test-password"
1552+
1553+ return req
1554+
1555+ @Object(parent="request_dev")
1556+ def get_customer_id_request(self, req=None):
1557+ req.nonce = uuid4().hex
1558+
1559+ return req
1560+
1561+ @Object(parent="request_test")
1562+ def get_customer_profile_request(self, req=None):
1563+ req.nonce = uuid4().hex
1564+
1565+ return req
1566+ ]]> </programlisting >
1567+
1568+ <para >
1569+ Same as with the other configuration modes, if you need to get an
1570+ abstract object from a container, use the .get_object's
1571+ <emphasis >ignore_abstract</emphasis > parameter,
1572+ otherwise <emphasis >springpython.container.AbstractObjectException</emphasis >
1573+ will be raised:
1574+ </para >
1575+
1576+ <programlisting ><![CDATA[
1577+ # .. skip creating the context
1578+
1579+ # No exception will be raised, even though 'request' is an abstract object
1580+ request = ctx.get_object("request", ignore_abstract=True)
1581+
1582+ # Will show the object
1583+ print request
1584+
1585+ # Will raise AbstractObjectException
1586+ request = ctx.get_object("request")
12531587]]> </programlisting >
12541588
12551589 </section >
0 commit comments