Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Moved version code out of __init__.py.

Moved everything except VERSION and a get_version() stub out of the top-level __init__.py, so that we're not importing all that stuff all the time. And because it's cleaner.
  • Loading branch information...
commit 5b644a5464e9612138dd7061abd386433bdc09f0 1 parent e7d99aa
Adrian Holovaty authored April 29, 2012
56  django/__init__.py
... ...
@@ -1,53 +1,7 @@
1  
-import datetime
2  
-import os
3  
-import subprocess
4  
-
5  
-
6 1
 VERSION = (1, 5, 0, 'alpha', 0)
7 2
 
8  
-
9  
-def get_version(version=None):
10  
-    """Derives a PEP386-compliant version number from VERSION."""
11  
-    if version is None:
12  
-        version = VERSION
13  
-    assert len(version) == 5
14  
-    assert version[3] in ('alpha', 'beta', 'rc', 'final')
15  
-
16  
-    # Now build the two parts of the version number:
17  
-    # main = X.Y[.Z]
18  
-    # sub = .devN - for pre-alpha releases
19  
-    #     | {a|b|c}N - for alpha, beta and rc releases
20  
-
21  
-    parts = 2 if version[2] == 0 else 3
22  
-    main = '.'.join(str(x) for x in version[:parts])
23  
-
24  
-    sub = ''
25  
-    if version[3] == 'alpha' and version[4] == 0:
26  
-        git_changeset = get_git_changeset()
27  
-        if git_changeset:
28  
-            sub = '.dev%s' % git_changeset
29  
-
30  
-    elif version[3] != 'final':
31  
-        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
32  
-        sub = mapping[version[3]] + str(version[4])
33  
-
34  
-    return main + sub
35  
-
36  
-
37  
-def get_git_changeset():
38  
-    """Returns a numeric identifier of the latest git changeset.
39  
-
40  
-    The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.
41  
-    This value isn't guaranteed to be unique but collisions are very unlikely,
42  
-    so it's sufficient for generating the development version numbers.
43  
-    """
44  
-    repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
45  
-    git_show = subprocess.Popen('git show --pretty=format:%ct --quiet HEAD',
46  
-            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
47  
-            shell=True, cwd=repo_dir, universal_newlines=True)
48  
-    timestamp = git_show.communicate()[0].partition('\n')[0]
49  
-    try:
50  
-        timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))
51  
-    except ValueError:
52  
-        return None
53  
-    return timestamp.strftime('%Y%m%d%H%M%S')
  3
+def get_version(*args, **kwargs):
  4
+    # Don't litter django/__init__.py with all the get_version stuff.
  5
+    # Only import if it's actually called.
  6
+    from django.utils.version import get_version
  7
+    return get_version(*args, **kwargs)
49  django/utils/version.py
... ...
@@ -0,0 +1,49 @@
  1
+import datetime
  2
+import os
  3
+import subprocess
  4
+
  5
+def get_version(version=None):
  6
+    "Returns a PEP 386-compliant version number from VERSION."
  7
+    if version is None:
  8
+        from django import VERSION as version
  9
+    else:
  10
+        assert len(version) == 5
  11
+        assert version[3] in ('alpha', 'beta', 'rc', 'final')
  12
+
  13
+    # Now build the two parts of the version number:
  14
+    # main = X.Y[.Z]
  15
+    # sub = .devN - for pre-alpha releases
  16
+    #     | {a|b|c}N - for alpha, beta and rc releases
  17
+
  18
+    parts = 2 if version[2] == 0 else 3
  19
+    main = '.'.join(str(x) for x in version[:parts])
  20
+
  21
+    sub = ''
  22
+    if version[3] == 'alpha' and version[4] == 0:
  23
+        git_changeset = get_git_changeset()
  24
+        if git_changeset:
  25
+            sub = '.dev%s' % git_changeset
  26
+
  27
+    elif version[3] != 'final':
  28
+        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
  29
+        sub = mapping[version[3]] + str(version[4])
  30
+
  31
+    return main + sub
  32
+
  33
+def get_git_changeset():
  34
+    """Returns a numeric identifier of the latest git changeset.
  35
+
  36
+    The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.
  37
+    This value isn't guaranteed to be unique, but collisions are very unlikely,
  38
+    so it's sufficient for generating the development version numbers.
  39
+    """
  40
+    repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  41
+    git_show = subprocess.Popen('git show --pretty=format:%ct --quiet HEAD',
  42
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  43
+            shell=True, cwd=repo_dir, universal_newlines=True)
  44
+    timestamp = git_show.communicate()[0].partition('\n')[0]
  45
+    try:
  46
+        timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))
  47
+    except ValueError:
  48
+        return None
  49
+    return timestamp.strftime('%Y%m%d%H%M%S')

13 notes on commit 5b644a5

Daniel Swarbrick

This is throwing AttributeError exceptions in ./manage.py runserver:

Exception AttributeError: AttributeError("'_DummyThread' object has no attribute '_Thread__block'",) in ignored

...and fails completely when using gevent-socketio's SocketIOServer. I don't think using subprocess.Popen is advisable in a multithreaded environment - at least not without some additional code.

Daniel Swarbrick

Resolved gevent's failure by changing monkey.patch_all() to monkey.patch_all(select=False).

I'm still not a fan of calling subprocesses from multiple threads. I expect webserver logs to start filling up with the DummyThread error mentioned above.

Adrian Holovaty

dswarbrick: Ah, interesting -- I can't reproduce but take your word for it. Do you know of another way of getting the hash of the current Git checkout? I'd love to avoid that subprocess call as well.

Daniel Swarbrick

I suspect the DummyThread error may only afflict Python 2.7 (http://bugs.python.org/issue14308). What version are you testing with?

I actually wrote a patch only hours before aaugustin opened a trac ticket with his patch. My patch obtained the git commit hash using only pure Python code (eg. no subprocess), but since aaugustin needs the timestamp of the commit, rather than the hash, this is a bit trickier to obtain. The Git Community Book describes in reasonable detail the format of the .git directory and pack files, so it would be feasible to get the timestamp without resorting to a subprocess.

Adrian Holovaty

I'm on Python 2.7.1.

I would love to see a pure Python solution. It's gotta be possible somehow. Are you willing to do the research/work?

Daniel Swarbrick

Sure... already on it ;-)

Jamie Rumbelow

Just to chip in here; there's a pure Python Git file/repo implementation called Dulwich. It'd be a good place to begin looking for a pure Python solution.

Daniel Swarbrick

I've just been looking at Dulwich. Obviously it's intended to be a fairly complete implementation of git, which is unnecessary in this case (otherwise Django could simply include a copy of Dulwich). Also, a lot of the class methods appear to raise NotImplementedError().

Drawing upon my earlier patch submission that ascertains the HEAD commit hash, I now have some rough Python code that parses the pack index(es) until it finds the HEAD commit object offset, then extracts the compressed object from the pack file. The Unix timestamp should then be easy to parse from there.

Jamie Rumbelow

To satisfy my professional curiosity please mention this issue in the commit message so it's linked... I'd like to have a look to see how you do it :)

Daniel Swarbrick

@adrianholovaty I have the code in a reasonable shape now. It's not quite in a form that can be pulled into Django, rather a little proof of concept script containing various git repo-parsing functions. I don't have a fork of Django currently, and suggest that I simply email or paste the code somewhere, until it can be manipulated into a form suitable for inclusion. Does that work for you?

It's about 80 lines of code, most of that spent parsing the pack index files and extracting the object from the pack file.

Adrian Holovaty

@dswarbrick I think 80 lines of code is overkill for the task at hand. :-/ But go ahead and paste it somewhere so we might be able to use it, or at least learn from it, in some way.

Daniel Swarbrick

@adrianholovaty I wish 80 LoC were an overkill, but parsing a git repo is considerably more involved than parsing a svn repo, due to the fact that a) objects can be either "loose objects", or stored in one of several pack files, b) if an object is not stored as a loose object, the pack indices need to be parsed in order to find the offset in the corresponding pack file where the object is located, c) the length of the object uses a somewhat bizarre variable length system, and d) only once the object is located and decompressed, do we actually get the timestamp of the commit. Fun.

Anyway.... here 'tis. http://pastebin.com/r9GLEQHS

Santanu Dey

OSQA still needs the def get_svn_revision()

Please sign in to comment.
Something went wrong with that request. Please try again.