[Checkins] SVN: zc.buildout/trunk/ Added the ability to load configuration from URLs.

Jim Fulton jim at zope.com
Thu Dec 7 16:34:36 EST 2006


Log message for revision 71496:
  Added the ability to load configuration from URLs.
  

Changed:
  U   zc.buildout/trunk/CHANGES.txt
  U   zc.buildout/trunk/setup.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.txt

-=-
Modified: zc.buildout/trunk/CHANGES.txt
===================================================================
--- zc.buildout/trunk/CHANGES.txt	2006-12-07 20:45:55 UTC (rev 71495)
+++ zc.buildout/trunk/CHANGES.txt	2006-12-07 21:34:35 UTC (rev 71496)
@@ -20,6 +20,14 @@
 Change History
 **************
 
+1.0.0b17 (2006-12-07)
+=====================
+
+Feature Changes
+---------------
+
+- Configuration files can now be loaded from URLs.
+
 1.0.0b16 (2006-12-07)
 =====================
 

Modified: zc.buildout/trunk/setup.py
===================================================================
--- zc.buildout/trunk/setup.py	2006-12-07 20:45:55 UTC (rev 71495)
+++ zc.buildout/trunk/setup.py	2006-12-07 21:34:35 UTC (rev 71496)
@@ -7,7 +7,7 @@
 name = "zc.buildout"
 setup(
     name = name,
-    version = "1.0.0b16",
+    version = "1.0.0b17",
     author = "Jim Fulton",
     author_email = "jim at zope.com",
     description = "System for managing development buildouts",

Modified: zc.buildout/trunk/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.py	2006-12-07 20:45:55 UTC (rev 71495)
+++ zc.buildout/trunk/src/zc/buildout/buildout.py	2006-12-07 21:34:35 UTC (rev 71496)
@@ -25,6 +25,7 @@
 import cStringIO
 import sys
 import tempfile
+import urllib2
 import ConfigParser
 import UserDict
 
@@ -41,6 +42,7 @@
 pkg_resources_loc = pkg_resources.working_set.find(
     pkg_resources.Requirement.parse('setuptools')).location
 
+_isurl = re.compile('([a-zA-Z0-9+.-]+)://').match
 
 class MissingOption(zc.buildout.UserError, KeyError):
     """A required option was missing
@@ -58,16 +60,11 @@
 
     def __init__(self, config_file, cloptions,
                  user_defaults=True, windows_restart=False):
-        config_file = os.path.abspath(config_file)
-        self._config_file = config_file
+
         self.__windows_restart = windows_restart
-        if not os.path.exists(config_file):
-            print 'Warning: creating', config_file
-            open(config_file, 'w').write('[buildout]\nparts = \n')
 
         # default options
         data = dict(buildout={
-            'directory': os.path.dirname(config_file),
             'eggs-directory': 'eggs',
             'develop-eggs-directory': 'develop-eggs',
             'bin-directory': 'bin',
@@ -79,6 +76,16 @@
             'log-format': '%(name)s: %(message)s',
             })
 
+        if not _isurl(config_file):
+            config_file = os.path.abspath(config_file)
+            base = os.path.dirname(config_file)
+            if not os.path.exists(config_file):
+                print 'Warning: creating', config_file
+                open(config_file, 'w').write('[buildout]\nparts = \n')
+            data['buildout']['directory'] = os.path.dirname(config_file)
+        else:
+            base = None
+
         # load user defaults, which override defaults
         if user_defaults and 'HOME' in os.environ:
             user_config = os.path.join(os.environ['HOME'],
@@ -98,6 +105,7 @@
             options[option] = value
                 # The egg dire
 
+
         self._raw = data
         self._data = {}
         self._parts = []
@@ -822,8 +830,6 @@
         if value.endswith('\n\t'):
             value = value[:-2] + '%(__buildout_space_n__)s'
         print >>f, option, '=', value
-            
-    
 
 def _open(base, filename, seen):
     """Open a configuration file and return the result as a dictionary,
@@ -831,18 +837,32 @@
     Recursively open other files based on buildout options found.
     """
 
-    filename = os.path.join(base, filename)
+    if _isurl(filename):
+        fp = urllib2.urlopen(filename)
+        base = filename[:filename.rfind('/')]
+    elif _isurl(base):
+        if os.path.isabs(filename):
+            fp = open(filename)
+            base = os.path.dirname(filename)
+        else:
+            filename = base + '/' + filename
+            fp = urllib2.urlopen(filename)
+            base = filename[:filename.rfind('/')]
+    else:
+        filename = os.path.join(base, filename)
+        fp = open(filename)
+        base = os.path.dirname(filename)
+
     if filename in seen:
         raise zc.buildout.UserError("Recursive file include", seen, filename)
 
-    base = os.path.dirname(filename)
     seen.append(filename)
 
     result = {}
 
     parser = ConfigParser.RawConfigParser()
     parser.optionxform = lambda s: s
-    parser.readfp(open(filename))
+    parser.readfp(fp)
     extends = extended_by = None
     for section in parser.sections():
         options = dict(parser.items(section))

Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-12-07 20:45:55 UTC (rev 71495)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-12-07 21:34:35 UTC (rev 71496)
@@ -741,6 +741,98 @@
 - Relative file names in extended options are interpreted relative to
   the directory containing the referencing configuration file.
 
+Loading Configuration from URLs
+-------------------------------
+
+Configuration files can be loaded from URLs.  To see how this works,
+we'll set up a web server with some configuration files.
+
+    >>> server_data = tmpdir('server_data')
+
+    >>> write(server_data, "r1.cfg",
+    ... """
+    ... [debug]
+    ... op1 = r1 1
+    ... op2 = r1 2
+    ... """)
+
+    >>> write(server_data, "r2.cfg",
+    ... """
+    ... [buildout]
+    ... extends = r1.cfg
+    ... 
+    ... [debug]
+    ... op2 = r2 2
+    ... op3 = r2 3
+    ... """)
+
+    >>> server_url = start_server(server_data)
+
+    >>> write('client.cfg', 
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = debug
+    ... extends = %(url)s/r2.cfg
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ... name = base
+    ... """ % dict(url=server_url))
+
+
+    >>> print system(buildout+ ' -c client.cfg'),
+    buildout: Develop: /sample-buildout/recipes
+    buildout: Uninstalling debug
+    buildout: Installing debug
+    name base
+    op1 r1 1
+    op2 r2 2
+    op3 r2 3
+    recipe recipes:debug
+
+Here we specified a URL for the file we extended.  The file we
+downloaded, itself refered to a file on the server using a relative
+URL reference.  Relative references are interpreted relative to the
+base URL when they appear in configuration files loaded via URL.
+
+We can also specify a URL as the configuration file to be used by a
+buildout.  
+
+    >>> os.remove('client.cfg')
+    >>> write(server_data, 'remote.cfg', 
+    ... """
+    ... [buildout]
+    ... develop = recipes
+    ... parts = debug
+    ... extends = r2.cfg
+    ...
+    ... [debug]
+    ... recipe = recipes:debug
+    ... name = remote
+    ... """)
+
+    >>> print system(buildout + ' -c ' + server_url + '/remote.cfg'),
+    Error: Missing option: buildout:directory
+
+Normally, the buildout directory defaults to directory
+containing a configuration file.  This won't work for configuration
+files loaded from URLs.  In this case, the buildout directory would
+normally be defined on the command line:
+
+    >>> print system(buildout
+    ...              + ' -c ' + server_url + '/remote.cfg'
+    ...              + ' buildout:directory=' + sample_buildout
+    ...              ),
+    buildout: Develop: /sample-buildout/recipes
+    buildout: Uninstalling debug
+    buildout: Installing debug
+    name remote
+    op1 r1 1
+    op2 r2 2
+    op3 r2 3
+    recipe recipes:debug
+
 User defaults
 -------------
 



More information about the Checkins mailing list