├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── setup.py ├── src ├── django_fields │ ├── __init__.py │ ├── fields.py │ ├── models.py │ └── tests.py ├── example │ ├── __init__.py │ ├── blog │ │ ├── __init__.py │ │ ├── admin.py │ │ └── models.py │ ├── manage.py │ ├── settings.py │ └── urls.py └── runtests.py ├── tox.ini └── virtualenv.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | *.egg-info 4 | database.db 5 | build 6 | dist 7 | .installed.cfg 8 | bin/ 9 | develop-eggs/ 10 | parts/ 11 | test.sqlite 12 | env 13 | pip-log.txt 14 | .tox 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | - "3.4" 5 | - "2.7" 6 | sudo: false 7 | env: 8 | - TOX_ENV=django19 9 | - TOX_ENV=django18 10 | - TOX_ENV=django110 11 | - TOX_ENV=djangomaster 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - env: TOX_ENV=djangomaster 16 | install: 17 | - pip install tox 18 | script: 19 | - tox -e $TOX_ENV 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | ========= 3 | 4 | 0.3.0 (2014-09-12) 5 | ------------------ 6 | 7 | * Added: Support for Django migrations (>=1.7) thanks to `Field.deconstruct()`. 8 | 9 | 0.2.3 10 | ----- 11 | 12 | * Added: Ability to specify `secret_key` as an argument to the field constructor (via [markkennedy](https://github.com/svetlyak40wt/django-fields/pull/40 "Issue #40")). 13 | * Added: `EncryptedUSPhoneNumberField` and `EncryptedUSSocialSecurityNumberField` now try to import from the standalone django-localflavor before falling back to the version included in Django (this is necessary to support Django 1.6 and newer) (via [mjacksonw](https://github.com/svetlyak40wt/django-fields/pull/36 "Issue #33")). 14 | 15 | 0.2.2 16 | ----- 17 | 18 | * Fixed: Django admin needs to be able to create blank instances of the fields in order to create a new model. This broke with `BaseEncryptedNumberField`. (via [defrex](https://github.com/svetlyak40wt/django-fields/pull/32 "Issue #32")) 19 | * Fixed: `block_type` wasn't added to the south rules. (via [defrex](https://github.com/svetlyak40wt/django-fields/pull/33 "Issue #33")) 20 | * Fixed: Newer code paths with `block_type` specified couldn't reuse the `cipher` object on the field class. `to_python` was already redefining it before decrypting the value, but `get_db_prep_value` wasn't before encrypting. The first time you used a model it would be fine, but the second would fail. Thus the tests were passing but the classes were functionally useless in an application. (via [defrex](https://github.com/svetlyak40wt/django-fields/pull/34 "Issue #34")) 21 | 22 | 0.2.1 23 | ----- 24 | 25 | * Added: `EncryptedUSSocialSecurityNumberField`, which handles the special-case logic of validating and encrypting US Social Security Numbers, using `django.contrib.localflavor.us.forms.USSocialSecurityNumberField`. (via [Brooks Travis](https://github.com/svetlyak40wt/django-fields/pull/24 "Pull Request 24")) 26 | * Fixed: Issue [#21](https://github.com/svetlyak40wt/django-fields/issues/21 "Issue #21"). 27 | * Changed: `django_fields.fields.BaseEncryptedField` now supports specification of cipher `block_type` via keyword argument. (via [kromem](https://github.com/svetlyak40wt/django-fields/pull/26 "Pull Request 26")) 28 | * Added: Deprecation warning for fields that do not specify a `block_type`. 29 | 30 | 0.2.0 31 | ----- 32 | 33 | * Added: Class `django_fields.models.ModelWithPrivateFields`, which allows to use private fields, starting from two underscores. 34 | * Fixed: `BaseEncryptedDateField.get_db_prep_value` errors. 35 | * Changed: Now virtualenv is used for test enviroment. Buildout.cfg was removed. 36 | 37 | 0.1.3 38 | ----- 39 | 40 | * Fixed: `EOFError` handling in `PickleField`. 41 | * Changed: Buildout file was changed to test against Django 1.2.5 and include `PyCrypto`. 42 | 43 | 0.1.2 44 | ----- 45 | 46 | * Added: `EncryptedEmail` and `USPhoneNumber` fields. 47 | * Added: `EncryptedNumber` fields. 48 | * Added: `EncryptedDateTimeField`. 49 | * Added: `EncryptedDateField` class. 50 | * Added: South support. 51 | * Added: Unit tests and associated utility functions. 52 | * Fixed: Deprecation warnings related to the settings in example project. 53 | * Fixed: Deprecation warnings, related to `get_db_prep_vasue`. 54 | * Fixed: Edge case in encryption consistency. 55 | * Changed: `EncryptedCharField` now enforces max length. 56 | 57 | 0.1.1 58 | ----- 59 | 60 | * Added: `PickleField` class. 61 | * Added: Encrypt field. 62 | * Added: Buildout config and example application. 63 | * Added: `setup.py` and `MANIFEST.in.` 64 | * Fixed: Issue #1 - "`EncryptedCharField` raises a traceback in the django admin". 65 | * Fixed: `max_length` issue. 66 | * Changed: Now `__import__` compatible with python 2.4 in `BaseEncryptedField.__init__`. 67 | * Changed: Code was moved to `src`. 68 | * Changed: Get rid of custom string class. It was replaced with string prefix. 69 | * Changed: Settings were changed to test with mysql. 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, Alexander Artemenko 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY Alexander Artemenko ''AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ------------ 3 | 4 | [![changelog](http://allmychanges.com/p/python/django-fields/badge/)](http://allmychanges.com/p/python/django-fields/?utm_source=badge) 5 | 6 | Django-fields is an application which includes different kinds of models fields. 7 | 8 | Right now, application contains two fields with encryption support: 9 | EncryptedCharField and EncryptedTextField. 10 | 11 | This project uses Travis for continuous integration: [![Build Status](https://secure.travis-ci.org/svetlyak40wt/django-fields.png)](http://travis-ci.org/svetlyak40wt/django-fields) 12 | 13 | 14 | Requirements 15 | ----------- 16 | 17 | This application depends on *python-crypto*, which can be found in many Linux 18 | repositories, or downloaded from http://www.dlitz.net/software/pycrypto/. 19 | 20 | Under Ubuntu, just do: 21 | 22 | sudo apt-get install python-crypto 23 | 24 | How to run tests 25 | ---------------- 26 | 27 | Examples can be found at the `examples` directory. Look at the, `tests.py`. 28 | Same project is used to run unittests. To run them, just fire `./run-tests.sh`. 29 | 30 | Contributors 31 | ------------ 32 | 33 | * [zbyte64](http://www.djangosnippets.org/users/zbyte64/) — thanks to for 34 | his [django snippet](http://www.djangosnippets.org/snippets/1095/) for encrypted 35 | fields. After some fixes, this snippet works as supposed. 36 | * John Morrissey — for fixing bug in PickleField. 37 | * Joe Jasinski — different fixes and new fields for encripted email and US Phone. 38 | * Colin MacDonald — for many encripted fields added. 39 | * Igor Davydenko — PickleField. 40 | * kromem - Added support for specifying `block_type` on encrypted fields. 41 | * Brooks Travis - new field for encrypted US Social Security Number and other fixes. 42 | 43 | 44 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/svetlyak40wt/django-fields/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 45 | 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = '0.3.0' 4 | 5 | setup( 6 | name='django-fields', 7 | version=version, 8 | description='Django-fields is an application which includes different kinds of models fields.', 9 | keywords='django apps tools collection', 10 | license='New BSD License', 11 | author='Alexander Artemenko', 12 | author_email='svetlyak.40wt@gmail.com', 13 | url='http://github.com/svetlyak40wt/django-fields/', 14 | install_requires=[ 15 | 'django', 16 | 'pycrypto', 17 | 'nose', 18 | 'django-nose==1.4.4', 19 | 'tox', 20 | ], 21 | classifiers=[ 22 | 'Development Status :: 2 - Pre-Alpha', 23 | 'Environment :: Plugins', 24 | 'Framework :: Django', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: BSD License', 27 | 'Programming Language :: Python', 28 | 'Topic :: Software Development :: Libraries :: Python Modules', 29 | ], 30 | package_dir={'': 'src'}, 31 | packages=['django_fields'], 32 | include_package_data=True, 33 | test_suite="runtests.runtests", 34 | ) 35 | -------------------------------------------------------------------------------- /src/django_fields/__init__.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import get_distribution, DistributionNotFound 2 | import os.path 3 | 4 | try: 5 | _dist = get_distribution('django-fields') 6 | except DistributionNotFound: 7 | __version__ = 'Please install this project with setup.py' 8 | else: 9 | __version__ = _dist.version 10 | VERSION = __version__ # synonym -------------------------------------------------------------------------------- /src/django_fields/fields.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import codecs 3 | import datetime 4 | import string 5 | import sys 6 | import warnings 7 | 8 | from django import forms 9 | from django.forms import fields 10 | from django.db import models 11 | from django.conf import settings 12 | from django.utils.translation import ugettext_lazy as _ 13 | from Crypto import Random 14 | from Crypto.Random import random 15 | 16 | if hasattr(settings, 'USE_CPICKLE'): 17 | warnings.warn( 18 | "The USE_CPICKLE options is now obsolete. cPickle will always " 19 | "be used unless it cannot be found or DEBUG=True", 20 | DeprecationWarning, 21 | ) 22 | 23 | if settings.DEBUG: 24 | import pickle 25 | else: 26 | try: 27 | import cPickle as pickle 28 | except: 29 | import pickle 30 | 31 | if sys.version_info[0] == 3: 32 | PYTHON3 = True 33 | from django.utils.encoding import smart_str, force_text as force_unicode 34 | else: 35 | PYTHON3 = False 36 | from django.utils.encoding import smart_str, force_unicode 37 | 38 | 39 | class BaseEncryptedField(models.Field): 40 | '''This code is based on the djangosnippet #1095 41 | You can find the original at http://www.djangosnippets.org/snippets/1095/''' 42 | 43 | def __init__(self, *args, **kwargs): 44 | self.cipher_type = kwargs.pop('cipher', 'AES') 45 | self.block_type = kwargs.pop('block_type', None) 46 | self.secret_key = kwargs.pop('secret_key', settings.SECRET_KEY) 47 | self.secret_key = self.secret_key[:32] 48 | 49 | if self.block_type is None: 50 | warnings.warn( 51 | "Default usage of pycrypto's AES block type defaults has been " 52 | "deprecated and will be removed in 0.3.0 (default will become " 53 | "MODE_CBC). Please specify a secure block_type, such as CBC.", 54 | DeprecationWarning, 55 | ) 56 | try: 57 | imp = __import__('Crypto.Cipher', globals(), locals(), [self.cipher_type], -1) 58 | except: 59 | imp = __import__('Crypto.Cipher', globals(), locals(), [self.cipher_type]) 60 | self.cipher_object = getattr(imp, self.cipher_type) 61 | if self.block_type: 62 | self.prefix = '$%s$%s$' % (self.cipher_type, self.block_type) 63 | self.iv = Random.new().read(self.cipher_object.block_size) 64 | self.cipher = self.cipher_object.new( 65 | self.secret_key, 66 | getattr(self.cipher_object, self.block_type), 67 | self.iv) 68 | else: 69 | self.cipher = self.cipher_object.new(self.secret_key) 70 | self.prefix = '$%s$' % self.cipher_type 71 | 72 | self.original_max_length = max_length = kwargs.get('max_length', 40) 73 | self.unencrypted_length = max_length 74 | # always add at least 2 to the max_length: 75 | # one for the null byte, one for padding 76 | max_length += 2 77 | mod = max_length % self.cipher.block_size 78 | if mod > 0: 79 | max_length += self.cipher.block_size - mod 80 | if self.block_type: 81 | max_length += len(self.iv) 82 | kwargs['max_length'] = max_length * 2 + len(self.prefix) 83 | 84 | super(BaseEncryptedField, self).__init__(*args, **kwargs) 85 | 86 | def _is_encrypted(self, value): 87 | if PYTHON3 is True: 88 | is_enc = isinstance(value, str) and value.startswith(self.prefix) 89 | return is_enc 90 | else: 91 | return isinstance(value, basestring) and value.startswith( 92 | self.prefix) 93 | 94 | def _get_padding(self, value): 95 | # We always want at least 2 chars of padding (including zero byte), 96 | # so we could have up to block_size + 1 chars. 97 | mod = (len(value) + 2) % self.cipher.block_size 98 | return self.cipher.block_size - mod + 2 99 | 100 | def from_db_value(self, value, expression, connection, context): 101 | if self._is_encrypted(value): 102 | if self.block_type: 103 | self.iv = binascii.a2b_hex(value[len(self.prefix):])[:len(self.iv)] 104 | self.cipher = self.cipher_object.new( 105 | self.secret_key, 106 | getattr(self.cipher_object, self.block_type), 107 | self.iv) 108 | decrypt_value = binascii.a2b_hex(value[len(self.prefix):])[len(self.iv):] 109 | else: 110 | decrypt_value = binascii.a2b_hex(value[len(self.prefix):]) 111 | return force_unicode( 112 | self.cipher.decrypt(decrypt_value).split(b'\0')[0] 113 | ) 114 | return value 115 | 116 | def get_db_prep_value(self, value, connection=None, prepared=False): 117 | if value is None: 118 | return None 119 | 120 | if PYTHON3 is True: 121 | value = bytes(value.encode('utf-8')) 122 | else: 123 | value = smart_str(value) 124 | 125 | if not self._is_encrypted(value): 126 | padding = self._get_padding(value) 127 | if padding > 0: 128 | if PYTHON3 is True: 129 | value += bytes("\0".encode('utf-8')) + bytes( 130 | ''.encode('utf-8')).join([ 131 | bytes(random.choice( 132 | string.printable).encode('utf-8')) 133 | for index in range(padding - 1)]) 134 | else: 135 | value += "\0" + ''.join([ 136 | random.choice(string.printable) 137 | for index in range(padding - 1) 138 | ]) 139 | if self.block_type: 140 | self.cipher = self.cipher_object.new( 141 | self.secret_key, 142 | getattr(self.cipher_object, self.block_type), 143 | self.iv) 144 | if PYTHON3 is True: 145 | value = self.prefix + binascii.b2a_hex( 146 | self.iv + self.cipher.encrypt(value)).decode('utf-8') 147 | else: 148 | value = self.prefix + binascii.b2a_hex( 149 | self.iv + self.cipher.encrypt(value)) 150 | else: 151 | if PYTHON3 is True: 152 | value = self.prefix + binascii.b2a_hex( 153 | self.cipher.encrypt(value)).decode('utf-8') 154 | else: 155 | value = self.prefix + binascii.b2a_hex( 156 | self.cipher.encrypt(value)) 157 | return value 158 | 159 | def deconstruct(self): 160 | original = super(BaseEncryptedField, self).deconstruct() 161 | kwargs = original[-1] 162 | if self.cipher_type != 'AES': 163 | kwargs['cipher'] = self.cipher_type 164 | if self.block_type is not None: 165 | kwargs['block_type'] = self.block_type 166 | if self.original_max_length != 40: 167 | kwargs['max_length'] = self.original_max_length 168 | return original[:-1] + (kwargs,) 169 | 170 | 171 | class EncryptedTextField(BaseEncryptedField): 172 | 173 | def get_internal_type(self): 174 | return 'TextField' 175 | 176 | def formfield(self, **kwargs): 177 | defaults = {'widget': forms.Textarea} 178 | defaults.update(kwargs) 179 | return super(EncryptedTextField, self).formfield(**defaults) 180 | 181 | 182 | class EncryptedCharField(BaseEncryptedField): 183 | 184 | def get_internal_type(self): 185 | return "CharField" 186 | 187 | def formfield(self, **kwargs): 188 | defaults = {'max_length': self.max_length} 189 | defaults.update(kwargs) 190 | return super(EncryptedCharField, self).formfield(**defaults) 191 | 192 | def get_db_prep_value(self, value, connection=None, prepared=False): 193 | if value is not None and not self._is_encrypted(value): 194 | if len(value) > self.unencrypted_length: 195 | raise ValueError( 196 | "Field value longer than max allowed: " + 197 | str(len(value)) + " > " + str(self.unencrypted_length) 198 | ) 199 | return super(EncryptedCharField, self).get_db_prep_value( 200 | value, 201 | connection=connection, 202 | prepared=prepared, 203 | ) 204 | 205 | 206 | class BaseEncryptedDateField(BaseEncryptedField): 207 | # Do NOT define a __metaclass__ for this - it's an abstract parent 208 | # for EncryptedDateField and EncryptedDateTimeField. 209 | # If you try to inherit from a class with a __metaclass__, you'll 210 | # get a very opaque infinite recursion in contribute_to_class. 211 | 212 | def __init__(self, *args, **kwargs): 213 | kwargs['max_length'] = self.max_raw_length 214 | super(BaseEncryptedDateField, self).__init__(*args, **kwargs) 215 | 216 | def get_internal_type(self): 217 | return 'CharField' 218 | 219 | def formfield(self, **kwargs): 220 | defaults = {'widget': self.form_widget, 'form_class': self.form_field} 221 | defaults.update(kwargs) 222 | return super(BaseEncryptedDateField, self).formfield(**defaults) 223 | 224 | def to_python(self, value): 225 | return self.from_db_value(value) 226 | 227 | def from_db_value(self, value, expression, connection, context): 228 | # value is either a date or a string in the format "YYYY:MM:DD" 229 | 230 | if value in fields.EMPTY_VALUES: 231 | date_value = value 232 | else: 233 | if isinstance(value, self.date_class): 234 | date_value = value 235 | else: 236 | date_text = super(BaseEncryptedDateField, self).from_db_value( 237 | value, expression, connection, context) 238 | date_value = self.date_class(*map(int, date_text.split(':'))) 239 | return date_value 240 | 241 | def get_db_prep_value(self, value, connection=None, prepared=False): 242 | # value is a date_class. 243 | # We need to convert it to a string in the format "YYYY:MM:DD" 244 | if value: 245 | date_text = value.strftime(self.save_format) 246 | else: 247 | date_text = None 248 | return super(BaseEncryptedDateField, self).get_db_prep_value( 249 | date_text, 250 | connection=connection, 251 | prepared=prepared 252 | ) 253 | 254 | 255 | class EncryptedDateField(BaseEncryptedDateField): 256 | form_widget = forms.DateInput 257 | form_field = forms.DateField 258 | save_format = "%Y:%m:%d" 259 | date_class = datetime.date 260 | max_raw_length = 10 # YYYY:MM:DD 261 | 262 | 263 | class EncryptedDateTimeField(BaseEncryptedDateField): 264 | # FIXME: This doesn't handle time zones, but Python doesn't really either. 265 | form_widget = forms.DateTimeInput 266 | form_field = forms.DateTimeField 267 | save_format = "%Y:%m:%d:%H:%M:%S:%f" 268 | date_class = datetime.datetime 269 | max_raw_length = 26 # YYYY:MM:DD:hh:mm:ss:micros 270 | 271 | 272 | class BaseEncryptedNumberField(BaseEncryptedField): 273 | # Do NOT define a __metaclass__ for this - it's abstract. 274 | # See BaseEncryptedDateField for full explanation. 275 | def __init__(self, *args, **kwargs): 276 | if self.max_raw_length: 277 | kwargs['max_length'] = self.max_raw_length 278 | super(BaseEncryptedNumberField, self).__init__(*args, **kwargs) 279 | 280 | def get_internal_type(self): 281 | return 'CharField' 282 | 283 | def to_python(self, value): 284 | return self.from_db_value(value) 285 | 286 | def from_db_value(self, value, expression, connection, context): 287 | # value is either an int or a string of an integer 288 | if isinstance(value, self.number_type) or value == '': 289 | number = value 290 | else: 291 | number_text = super(BaseEncryptedNumberField, self).from_db_value( 292 | value, expression, connection, context) 293 | number = self.number_type(number_text) 294 | return number 295 | 296 | # def get_prep_value(self, value): 297 | def get_db_prep_value(self, value, connection=None, prepared=False): 298 | number_text = self.format_string % value 299 | return super(BaseEncryptedNumberField, self).get_db_prep_value( 300 | number_text, 301 | connection=connection, 302 | prepared=prepared, 303 | ) 304 | 305 | 306 | class EncryptedIntField(BaseEncryptedNumberField): 307 | if PYTHON3 is True: 308 | max_raw_length = len(str(-sys.maxsize - 1)) 309 | else: 310 | max_raw_length = len(str(-sys.maxint - 1)) 311 | number_type = int 312 | format_string = "%d" 313 | 314 | 315 | class EncryptedLongField(BaseEncryptedNumberField): 316 | max_raw_length = None # no limit 317 | if PYTHON3 is True: 318 | number_type = int 319 | else: 320 | number_type = long 321 | format_string = "%d" 322 | 323 | def get_internal_type(self): 324 | return 'TextField' 325 | 326 | 327 | class EncryptedFloatField(BaseEncryptedNumberField): 328 | max_raw_length = 150 # arbitrary, but should be sufficient 329 | number_type = float 330 | # If this format is too long for some architectures, change it. 331 | format_string = "%0.66f" 332 | 333 | 334 | class PickleField(models.TextField): 335 | editable = False 336 | serialize = False 337 | 338 | def get_db_prep_value(self, value, connection=None, prepared=False): 339 | if PYTHON3 is True: 340 | # When PYTHON3, we convert data to base64 to prevent errors when 341 | # unpickling. 342 | val = codecs.encode(pickle.dumps(value), 'base64').decode() 343 | return val 344 | else: 345 | return pickle.dumps(value) 346 | 347 | def to_python(self, value): 348 | return self.from_db_value(value) 349 | 350 | def from_db_value(self, value, expression, connection, context): 351 | if PYTHON3 is True: 352 | if not isinstance(value, str): 353 | return value 354 | else: 355 | if not isinstance(value, basestring): 356 | return value 357 | 358 | # Tries to convert unicode objects to string, cause loads pickle from 359 | # unicode excepts ugly ``KeyError: '\x00'``. 360 | try: 361 | if PYTHON3 is True: 362 | # When PYTHON3, data are in base64 to prevent errors when 363 | # unpickling. 364 | val = pickle.loads(codecs.decode(value.encode(), "base64")) 365 | return val 366 | else: 367 | return pickle.loads(smart_str(value)) 368 | # If pickle could not loads from string it's means that it's Python 369 | # string saved to PickleField. 370 | except ValueError: 371 | return value 372 | except EOFError: 373 | return value 374 | 375 | 376 | class EncryptedUSPhoneNumberField(BaseEncryptedField): 377 | def get_internal_type(self): 378 | return "CharField" 379 | 380 | def formfield(self, **kwargs): 381 | try: 382 | from localflavor.us.forms import USPhoneNumberField 383 | except ImportError: 384 | from django.contrib.localflavor.us.forms import USPhoneNumberField 385 | 386 | defaults = {'form_class': USPhoneNumberField} 387 | defaults.update(kwargs) 388 | return super(EncryptedUSPhoneNumberField, self).formfield(**defaults) 389 | 390 | 391 | class EncryptedUSSocialSecurityNumberField(BaseEncryptedField): 392 | def get_internal_type(self): 393 | return "CharField" 394 | 395 | def formfield(self, **kwargs): 396 | try: 397 | from localflavor.us.forms import USSocialSecurityNumberField 398 | except ImportError: 399 | from django.contrib.localflavor.us.forms import USSocialSecurityNumberField 400 | 401 | defaults = {'form_class': USSocialSecurityNumberField} 402 | defaults.update(kwargs) 403 | return super(EncryptedUSSocialSecurityNumberField, self).formfield(**defaults) 404 | 405 | 406 | class EncryptedEmailField(BaseEncryptedField): 407 | description = _("E-mail address") 408 | 409 | def get_internal_type(self): 410 | return "CharField" 411 | 412 | def formfield(self, **kwargs): 413 | from django.forms import EmailField 414 | defaults = {'form_class': EmailField, 'max_length': self.unencrypted_length} 415 | defaults.update(kwargs) 416 | return super(EncryptedEmailField, self).formfield(**defaults) 417 | 418 | 419 | try: 420 | from south.modelsinspector import add_introspection_rules 421 | add_introspection_rules([ 422 | ( 423 | [ 424 | BaseEncryptedField, EncryptedDateField, BaseEncryptedDateField, EncryptedCharField, EncryptedTextField, 425 | EncryptedFloatField, EncryptedDateTimeField, BaseEncryptedNumberField, EncryptedIntField, EncryptedLongField, 426 | EncryptedUSPhoneNumberField, EncryptedEmailField, 427 | ], 428 | [], 429 | { 430 | 'cipher': ('cipher_type', {}), 431 | 'block_type': ('block_type', {}), 432 | }, 433 | ), 434 | ], ["^django_fields\.fields\..+?Field"]) 435 | add_introspection_rules([], ["^django_fields\.fields\.PickleField"]) 436 | except ImportError: 437 | pass 438 | -------------------------------------------------------------------------------- /src/django_fields/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | 5 | from django.db import models 6 | 7 | if sys.version_info[0] == 3: 8 | PYTHON3 = True 9 | else: 10 | PYTHON3 = False 11 | 12 | 13 | class PrivateFieldsMetaclass(models.base.ModelBase): 14 | """Metaclass to set right default db_column values 15 | for mangled private fields. 16 | 17 | For example, python transforms field __secret_state of the model 18 | Machine, to the _Machine__secret_state and you don't want 19 | database column has this ugly name. There are two possibilities: 20 | 21 | * to specify column name via arguments to django Field class, or… 22 | * to use this PrivateFieldsMetaclass which will say django: 23 | "Hey, for this field `_Machine__secret_state` use db_column 24 | `secret_state`, please!" 25 | 26 | """ 27 | def __new__(cls, name, bases, attrs): 28 | super_new = super(PrivateFieldsMetaclass, cls).__new__ 29 | 30 | prefix = '_' + name + '__' 31 | for key, value in attrs.iteritems(): 32 | if key.startswith(prefix) and hasattr(value, 'db_column') and value.db_column is None: 33 | value.db_column = key[len(prefix):] 34 | 35 | result = super_new(cls, name, bases, attrs) 36 | return result 37 | 38 | -------------------------------------------------------------------------------- /src/django_fields/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | import datetime 5 | import re 6 | import string 7 | import sys 8 | import unittest 9 | 10 | import django 11 | from django.db import connection 12 | from django.db import models 13 | 14 | from .fields import ( 15 | EncryptedCharField, EncryptedDateField, 16 | EncryptedDateTimeField, EncryptedIntField, 17 | EncryptedLongField, EncryptedFloatField, PickleField, 18 | EncryptedUSPhoneNumberField, EncryptedUSSocialSecurityNumberField, 19 | EncryptedEmailField, 20 | ) 21 | 22 | if django.VERSION[1] > 9: 23 | DJANGO_1_10 = True 24 | else: 25 | DJANGO_1_10 = False 26 | 27 | if sys.version_info[0] == 3: 28 | PYTHON3 = True 29 | else: 30 | PYTHON3 = False 31 | 32 | 33 | class EncObject(models.Model): 34 | max_password = 100 35 | password = EncryptedCharField(max_length=max_password, null=True) 36 | 37 | class Meta: 38 | app_label = 'django_fields' 39 | 40 | 41 | class EncDate(models.Model): 42 | important_date = EncryptedDateField() 43 | 44 | class Meta: 45 | app_label = 'django_fields' 46 | 47 | 48 | class EncDateTime(models.Model): 49 | important_datetime = EncryptedDateTimeField() 50 | # important_datetime = EncryptedDateField() 51 | 52 | class Meta: 53 | app_label = 'django_fields' 54 | 55 | 56 | class EncInt(models.Model): 57 | important_number = EncryptedIntField() 58 | 59 | class Meta: 60 | app_label = 'django_fields' 61 | 62 | 63 | class EncLong(models.Model): 64 | important_number = EncryptedLongField() 65 | 66 | class Meta: 67 | app_label = 'django_fields' 68 | 69 | 70 | class EncFloat(models.Model): 71 | important_number = EncryptedFloatField() 72 | 73 | class Meta: 74 | app_label = 'django_fields' 75 | 76 | 77 | class PickleObject(models.Model): 78 | name = models.CharField(max_length=16) 79 | data = PickleField() 80 | 81 | class Meta: 82 | app_label = 'django_fields' 83 | 84 | 85 | class EmailObject(models.Model): 86 | max_email = 255 87 | email = EncryptedEmailField(max_length=max_email) 88 | 89 | class Meta: 90 | app_label = 'django_fields' 91 | 92 | 93 | class USPhoneNumberField(models.Model): 94 | phone = EncryptedUSPhoneNumberField() 95 | 96 | class Meta: 97 | app_label = 'django_fields' 98 | 99 | 100 | class USSocialSecurityNumberField(models.Model): 101 | ssn = EncryptedUSSocialSecurityNumberField() 102 | 103 | class Meta: 104 | app_label = 'django_fields' 105 | 106 | 107 | class CipherEncObject(models.Model): 108 | max_password = 20 109 | password = EncryptedCharField( 110 | max_length=max_password, 111 | block_type='MODE_CBC') 112 | 113 | class Meta: 114 | app_label = 'django_fields' 115 | 116 | 117 | class CipherEncDate(models.Model): 118 | important_date = EncryptedDateField(block_type='MODE_CBC') 119 | 120 | class Meta: 121 | app_label = 'django_fields' 122 | 123 | 124 | class EncryptTests(unittest.TestCase): 125 | 126 | def setUp(self): 127 | EncObject.objects.all().delete() 128 | 129 | def test_encryption(self): 130 | """ 131 | Test that the database values are actually encrypted. 132 | """ 133 | password = 'this is a password!!' # 20 chars 134 | obj = EncObject(password = password) 135 | obj.save() 136 | # The value from the retrieved object should be the same... 137 | obj = EncObject.objects.get(id=obj.id) 138 | self.assertEqual(password, obj.password) 139 | # ...but the value in the database should not 140 | encrypted_password = self._get_encrypted_password(obj.id) 141 | self.assertNotEqual(encrypted_password, password) 142 | self.assertTrue(encrypted_password.startswith('$AES$')) 143 | 144 | def test_encryption_w_cipher(self): 145 | """ 146 | Test that the database values are actually encrypted when using 147 | non-default cipher types. 148 | """ 149 | password = 'this is a password!!' # 20 chars 150 | obj = CipherEncObject(password = password) 151 | obj.save() 152 | # The value from the retrieved object should be the same... 153 | obj = CipherEncObject.objects.get(id=obj.id) 154 | self.assertEqual(password, obj.password) 155 | # ...but the value in the database should not 156 | encrypted_password = self._get_encrypted_password_cipher(obj.id) 157 | self.assertNotEqual(encrypted_password, password) 158 | self.assertTrue(encrypted_password.startswith('$AES$MODE_CBC$')) 159 | 160 | def test_multiple_encryption_w_cipher(self): 161 | """ 162 | Test that a single field can be reused without error. 163 | """ 164 | password = 'this is a password!!' 165 | obj = CipherEncObject(password=password) 166 | obj.save() 167 | obj = CipherEncObject.objects.get(id=obj.id) 168 | self.assertEqual(password, obj.password) 169 | 170 | password = 'another password!!' 171 | obj = CipherEncObject(password=password) 172 | obj.save() 173 | obj = CipherEncObject.objects.get(id=obj.id) 174 | self.assertEqual(password, obj.password) 175 | 176 | def test_max_field_length(self): 177 | password = 'a' * EncObject.max_password 178 | obj = EncObject(password = password) 179 | obj.save() 180 | obj = EncObject.objects.get(id=obj.id) 181 | self.assertEqual(password, obj.password) 182 | 183 | def test_field_too_long(self): 184 | password = 'a' * (EncObject.max_password + 1) 185 | obj = EncObject(password = password) 186 | self.assertRaises(Exception, obj.save) 187 | 188 | def test_UTF8(self): 189 | password = u'совершенно секретно' 190 | obj = EncObject(password = password) 191 | obj.save() 192 | obj = EncObject.objects.get(id=obj.id) 193 | self.assertEqual(password, obj.password) 194 | 195 | def test_consistent_encryption(self): 196 | """ 197 | The same password should not encrypt the same way twice. 198 | Check different lengths. 199 | """ 200 | # NOTE: This may fail occasionally because the randomly-generated padding could be the same for both values. 201 | # A 14-char string will only have 1 char of padding. There's a 1/len(string.printable) chance of getting the 202 | # same value twice. 203 | for pwd_length in range(1,21): # 1-20 inclusive 204 | enc_pwd_1, enc_pwd_2 = self._get_two_passwords(pwd_length) 205 | self.assertNotEqual(enc_pwd_1, enc_pwd_2) 206 | 207 | def test_minimum_padding(self): 208 | """ 209 | There should always be at least two chars of padding. 210 | """ 211 | enc_field = EncryptedCharField() 212 | for pwd_length in range(1,21): # 1-20 inclusive 213 | password = 'a' * pwd_length # 'a', 'aa', ... 214 | self.assertTrue(enc_field._get_padding(password) >= 2) 215 | 216 | def test_none_value(self): 217 | """ 218 | A value of None should be passed through without encryption. 219 | """ 220 | obj = EncObject(password=None) 221 | obj.save() 222 | obj = EncObject.objects.get(id=obj.id) 223 | self.assertEqual(obj.password, None) 224 | encrypted_text = self._get_encrypted_password(obj.id) 225 | self.assertEqual(encrypted_text, None) 226 | 227 | ### Utility methods for tests ### 228 | 229 | def _get_encrypted_password(self, id): 230 | cursor = connection.cursor() 231 | cursor.execute("select password from django_fields_encobject where id = %s", [id,]) 232 | passwords = list(map(lambda x: x[0], cursor.fetchall())) 233 | self.assertEqual(len(passwords), 1) # only one 234 | return passwords[0] 235 | 236 | def _get_encrypted_password_cipher(self, id): 237 | cursor = connection.cursor() 238 | cursor.execute("select password from django_fields_cipherencobject where id = %s", [id,]) 239 | passwords = list(map(lambda x: x[0], cursor.fetchall())) 240 | self.assertEqual(len(passwords), 1) # only one 241 | return passwords[0] 242 | 243 | def _get_two_passwords(self, pwd_length): 244 | password = 'a' * pwd_length # 'a', 'aa', ... 245 | obj_1 = EncObject(password = password) 246 | obj_1.save() 247 | obj_2 = EncObject(password = password) 248 | obj_2.save() 249 | # The encrypted values in the database should be different. 250 | # There's a chance they'll be the same, but it's small. 251 | enc_pwd_1 = self._get_encrypted_password(obj_1.id) 252 | enc_pwd_2 = self._get_encrypted_password(obj_2.id) 253 | return enc_pwd_1, enc_pwd_2 254 | 255 | 256 | class DateEncryptTests(unittest.TestCase): 257 | def setUp(self): 258 | EncDate.objects.all().delete() 259 | 260 | def test_BC_date(self): 261 | # datetime.MINYEAR is 1 -- so much for history 262 | func = lambda: datetime.date(0, 1, 1) 263 | self.assertRaises(ValueError, func) 264 | 265 | def test_date_encryption(self): 266 | today = datetime.date.today() 267 | obj = EncDate(important_date=today) 268 | obj.save() 269 | # The date from the retrieved object should be the same... 270 | obj = EncDate.objects.get(id=obj.id) 271 | self.assertEqual(today, obj.important_date) 272 | # ...but the value in the database should not 273 | important_date = self._get_encrypted_date(obj.id) 274 | self.assertTrue(important_date.startswith('$AES$')) 275 | self.assertNotEqual(important_date, today) 276 | 277 | def test_date_time_encryption(self): 278 | now = datetime.datetime.now() 279 | obj = EncDateTime(important_datetime=now) 280 | obj.save() 281 | # The datetime from the retrieved object should be the same... 282 | obj = EncDateTime.objects.get(id=obj.id) 283 | self.assertEqual(now, obj.important_datetime) 284 | # ...but the value in the database should not 285 | important_datetime = self._get_encrypted_datetime(obj.id) 286 | self.assertTrue(important_datetime.startswith('$AES$')) 287 | self.assertNotEqual(important_datetime, now) 288 | 289 | def test_date_encryption_w_cipher(self): 290 | today = datetime.date.today() 291 | obj = CipherEncDate(important_date=today) 292 | obj.save() 293 | # The date from the retrieved object should be the same... 294 | obj = CipherEncDate.objects.get(id=obj.id) 295 | self.assertEqual(today, obj.important_date) 296 | # ...but the value in the database should not 297 | important_date = self._get_encrypted_date_cipher(obj.id) 298 | self.assertTrue(important_date.startswith('$AES$MODE_CBC$')) 299 | self.assertNotEqual(important_date, today) 300 | 301 | ### Utility methods for tests ### 302 | 303 | def _get_encrypted_date(self, id): 304 | cursor = connection.cursor() 305 | cursor.execute("select important_date from django_fields_encdate where id = %s", [id,]) 306 | important_dates = list(map(lambda x: x[0], cursor.fetchall())) 307 | self.assertEqual(len(important_dates), 1) # only one 308 | return important_dates[0] 309 | 310 | def _get_encrypted_datetime(self, id): 311 | cursor = connection.cursor() 312 | cursor.execute("select important_datetime from django_fields_encdatetime where id = %s", [id,]) 313 | important_datetimes = list(map(lambda x: x[0], cursor.fetchall())) 314 | self.assertEqual(len(important_datetimes), 1) # only one 315 | return important_datetimes[0] 316 | 317 | def _get_encrypted_date_cipher(self, id): 318 | cursor = connection.cursor() 319 | cursor.execute("select important_date from django_fields_cipherencdate where id = %s", [id,]) 320 | important_dates = list(map(lambda x: x[0], cursor.fetchall())) 321 | self.assertEqual(len(important_dates), 1) # only one 322 | return important_dates[0] 323 | 324 | 325 | class NumberEncryptTests(unittest.TestCase): 326 | def setUp(self): 327 | EncInt.objects.all().delete() 328 | EncLong.objects.all().delete() 329 | EncFloat.objects.all().delete() 330 | 331 | def test_int_encryption(self): 332 | if PYTHON3 is True: 333 | self._test_number_encryption(EncInt, 'int', sys.maxsize) 334 | else: 335 | self._test_number_encryption(EncInt, 'int', sys.maxint) 336 | 337 | def test_min_int_encryption(self): 338 | if PYTHON3 is True: 339 | self._test_number_encryption(EncInt, 'int', -sys.maxsize - 1) 340 | else: 341 | self._test_number_encryption(EncInt, 'int', -sys.maxint - 1) 342 | 343 | def test_long_encryption(self): 344 | if PYTHON3 is True: 345 | self._test_number_encryption( 346 | EncLong, 'long', int(sys.maxsize) * 100) 347 | else: 348 | self._test_number_encryption( 349 | EncLong, 'long', long(sys.maxint) * long(100)) 350 | 351 | def test_float_encryption(self): 352 | if PYTHON3 is True: 353 | value = 123.456 + sys.maxsize 354 | else: 355 | value = 123.456 + sys.maxint 356 | self._test_number_encryption(EncFloat, 'float', value) 357 | 358 | def test_one_third_float_encryption(self): 359 | if PYTHON3 is True: 360 | value = sys.maxsize + (1.0 / 3.0) 361 | else: 362 | value = sys.maxint + (1.0 / 3.0) 363 | self._test_number_encryption(EncFloat, 'float', value) 364 | 365 | def _test_number_encryption(self, number_class, type_name, value): 366 | obj = number_class(important_number=value) 367 | obj.save() 368 | # The int from the retrieved object should be the same... 369 | obj = number_class.objects.get(id=obj.id) 370 | self.assertEqual(value, obj.important_number) 371 | # ...but the value in the database should not 372 | number = self._get_encrypted_number(type_name, obj.id) 373 | self.assertTrue(number.startswith('$AES$')) 374 | self.assertNotEqual(number, value) 375 | 376 | def _get_encrypted_number(self, type_name, id): 377 | cursor = connection.cursor() 378 | sql = "select important_number from django_fields_enc%s where id = %%s" % (type_name,) 379 | cursor.execute(sql, [id,]) 380 | important_numbers = list(map(lambda x: x[0], cursor.fetchall())) 381 | self.assertEqual(len(important_numbers), 1) # only one 382 | return important_numbers[0] 383 | 384 | 385 | class TestPickleField(unittest.TestCase): 386 | def setUp(self): 387 | PickleObject.objects.all().delete() 388 | 389 | def test_not_string_data(self): 390 | items = [ 391 | 'Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5' 392 | ] 393 | 394 | obj = PickleObject.objects.create(name='default', data=items) 395 | self.assertEqual(PickleObject.objects.count(), 1) 396 | 397 | self.assertEqual(obj.data, items) 398 | 399 | obj = PickleObject.objects.get(name='default') 400 | self.assertEqual(obj.data, items) 401 | 402 | def test_string_and_unicode_data(self): 403 | DATA = ( 404 | ('string', 'Simple string'), 405 | ('unicode', u'Simple unicode string'), 406 | ) 407 | 408 | for name, data in DATA: 409 | obj = PickleObject.objects.create(name=name, data=data) 410 | self.assertEqual(obj.data, data) 411 | 412 | self.assertEqual(PickleObject.objects.count(), 2) 413 | 414 | for name, data in DATA: 415 | obj = PickleObject.objects.get(name=name) 416 | self.assertEqual(obj.data, data) 417 | 418 | def test_empty_string(self): 419 | value = '' 420 | 421 | obj = PickleObject.objects.create(name='default', data=value) 422 | self.assertEqual(PickleObject.objects.count(), 1) 423 | 424 | self.assertEqual(obj.data, value) 425 | 426 | 427 | class EncryptEmailTests(unittest.TestCase): 428 | 429 | def setUp(self): 430 | EmailObject.objects.all().delete() 431 | 432 | def test_encryption(self): 433 | """ 434 | Test that the database values are actually encrypted. 435 | """ 436 | email = 'test@example.com' # 16 chars 437 | obj = EmailObject(email = email) 438 | obj.save() 439 | # The value from the retrieved object should be the same... 440 | obj = EmailObject.objects.get(id=obj.id) 441 | self.assertEqual(email, obj.email) 442 | # ...but the value in the database should not 443 | encrypted_email = self._get_encrypted_email(obj.id) 444 | self.assertNotEqual(encrypted_email, email) 445 | self.assertTrue(encrypted_email.startswith('$AES$')) 446 | 447 | def test_max_field_length(self): 448 | email = 'a' * EmailObject.max_email 449 | obj = EmailObject(email = email) 450 | obj.save() 451 | obj = EmailObject.objects.get(id=obj.id) 452 | self.assertEqual(email, obj.email) 453 | 454 | def test_UTF8(self): 455 | email = u'совершенно@секретно.com' 456 | obj = EmailObject(email = email) 457 | obj.save() 458 | obj = EmailObject.objects.get(id=obj.id) 459 | self.assertEqual(email, obj.email) 460 | 461 | def test_consistent_encryption(self): 462 | """ 463 | The same password should not encrypt the same way twice. 464 | Check different lengths. 465 | """ 466 | # NOTE: This may fail occasionally because the randomly-generated padding could be the same for both values. 467 | # A 14-char string will only have 1 char of padding. There's a 1/len(string.printable) chance of getting the 468 | # same value twice. 469 | for email_length in range(1,21): # 1-20 inclusive 470 | enc_email_1, enc_email_2 = self._get_two_emails(email_length) 471 | self.assertNotEqual(enc_email_1, enc_email_2) 472 | 473 | def test_minimum_padding(self): 474 | """ 475 | There should always be at least two chars of padding. 476 | """ 477 | enc_field = EncryptedCharField() 478 | for pwd_length in range(1,21): # 1-20 inclusive 479 | email = 'a' * pwd_length # 'a', 'aa', ... 480 | self.assertTrue(enc_field._get_padding(email) >= 2) 481 | 482 | ### Utility methods for tests ### 483 | 484 | def _get_encrypted_email(self, id): 485 | cursor = connection.cursor() 486 | cursor.execute("select email from django_fields_emailobject where id = %s", [id,]) 487 | emails = list(map(lambda x: x[0], cursor.fetchall())) 488 | self.assertEqual(len(emails), 1) # only one 489 | return emails[0] 490 | 491 | def _get_two_emails(self, email_length): 492 | email = 'a' * email_length # 'a', 'aa', ... 493 | obj_1 = EmailObject(email = email) 494 | obj_1.save() 495 | obj_2 = EmailObject(email = email) 496 | obj_2.save() 497 | # The encrypted values in the database should be different. 498 | # There's a chance they'll be the same, but it's small. 499 | enc_email_1 = self._get_encrypted_email(obj_1.id) 500 | enc_email_2 = self._get_encrypted_email(obj_2.id) 501 | return enc_email_1, enc_email_2 502 | 503 | 504 | 505 | class DatabaseSchemaTests(unittest.TestCase): 506 | def test_cipher_storage_length_versus_schema_length(self): 507 | password = 'this is a password!!' # 20 chars 508 | obj = CipherEncObject(password=password) 509 | obj.save() 510 | # Get the raw (encrypted) value from the database 511 | raw_value = self._get_raw_password_value(obj.id) 512 | column_width = self._get_password_field_column_width() 513 | # The raw value should fit within the column width 514 | self.assertLessEqual(len(raw_value), column_width) 515 | 516 | ### Utility methods for tests ### 517 | 518 | def _get_raw_password_value(self, id): 519 | cursor = connection.cursor() 520 | cursor.execute("select password from django_fields_cipherencobject where id = %s", [id, ]) 521 | passwords = list(map(lambda x: x[0], cursor.fetchall())) 522 | self.assertEqual(len(passwords), 1) # only one 523 | return passwords[0] 524 | 525 | def _get_password_field_column_width(self): 526 | # This only works in SQLite; if you change the 527 | # type of database used for testing, the type 528 | # returned from get_table_description might be 529 | # different! 530 | cursor = connection.cursor() 531 | table_description = connection.introspection.get_table_description(cursor, 'django_fields_cipherencobject') 532 | # The first field in the tuple is the column name 533 | password_field = [field for field in table_description if field[0] == 'password'] 534 | self.assertEqual(len(password_field), 1) 535 | password_field = password_field[0] 536 | # if django < 1.10 537 | # The second field contains the type; 538 | # this is something like u'varchar(78)' 539 | if DJANGO_1_10 is False: 540 | raw_type = password_field[1] 541 | matches = re.match('varchar\((\d+)\)', raw_type.lower()) 542 | self.assertNotEqual(matches, None) 543 | column_width = int(matches.groups()[0]) 544 | return column_width 545 | else: 546 | raw_type = password_field.internal_size 547 | return raw_type 548 | -------------------------------------------------------------------------------- /src/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svetlyak40wt/django-fields/efef8e8ac3f495e4fa54cdbd1abe8337f80140b7/src/example/__init__.py -------------------------------------------------------------------------------- /src/example/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svetlyak40wt/django-fields/efef8e8ac3f495e4fa54cdbd1abe8337f80140b7/src/example/blog/__init__.py -------------------------------------------------------------------------------- /src/example/blog/admin.py: -------------------------------------------------------------------------------- 1 | from example.blog.models import EncObject 2 | from django.contrib import admin 3 | 4 | admin.site.register(EncObject) 5 | -------------------------------------------------------------------------------- /src/example/blog/models.py: -------------------------------------------------------------------------------- 1 | from django_fields.tests import EncObject 2 | -------------------------------------------------------------------------------- /src/example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /src/example/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@domain.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = dict( 13 | default = dict( 14 | ENGINE = 'django.db.backends.sqlite3', 15 | NAME = 'test.sqlite', 16 | ) 17 | ) 18 | 19 | # Local time zone for this installation. Choices can be found here: 20 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 21 | # although not all choices may be available on all operating systems. 22 | # If running in a Windows environment this must be set to the same as your 23 | # system time zone. 24 | TIME_ZONE = 'America/Chicago' 25 | 26 | # Language code for this installation. All choices can be found here: 27 | # http://www.i18nguy.com/unicode/language-identifiers.html 28 | LANGUAGE_CODE = 'en-us' 29 | 30 | SITE_ID = 1 31 | 32 | # If you set this to False, Django will make some optimizations so as not 33 | # to load the internationalization machinery. 34 | USE_I18N = True 35 | 36 | # Absolute path to the directory that holds media. 37 | # Example: "/home/media/media.lawrence.com/" 38 | MEDIA_ROOT = '' 39 | 40 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 41 | # trailing slash if there is a path component (optional in other cases). 42 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 43 | MEDIA_URL = '' 44 | 45 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 46 | # trailing slash. 47 | # Examples: "http://foo.com/media/", "/media/". 48 | ADMIN_MEDIA_PREFIX = '/media/' 49 | 50 | # Make this unique, and don't share it with anybody. 51 | SECRET_KEY = '=r521(6qmkk#uus#gpiml@5+26@_qcj^tmk0%12byrd6=qh954' 52 | 53 | # List of callables that know how to import templates from various sources. 54 | TEMPLATE_LOADERS = ( 55 | 'django.template.loaders.filesystem.load_template_source', 56 | 'django.template.loaders.app_directories.load_template_source', 57 | # 'django.template.loaders.eggs.load_template_source', 58 | ) 59 | 60 | MIDDLEWARE_CLASSES = ( 61 | 'django.middleware.common.CommonMiddleware', 62 | 'django.contrib.sessions.middleware.SessionMiddleware', 63 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 64 | ) 65 | 66 | ROOT_URLCONF = 'example.urls' 67 | 68 | TEMPLATE_DIRS = ( 69 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 70 | # Always use forward slashes, even on Windows. 71 | # Don't forget to use absolute paths, not relative paths. 72 | ) 73 | 74 | INSTALLED_APPS = ( 75 | 'django.contrib.auth', 76 | 'django.contrib.contenttypes', 77 | 'django.contrib.sessions', 78 | 'django.contrib.sites', 79 | 'django.contrib.admin', 80 | 'django_fields', 81 | 'django_nose', 82 | 'example.blog', 83 | ) 84 | 85 | TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' 86 | -------------------------------------------------------------------------------- /src/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Example: 9 | # (r'^example/', include('example.foo.urls')), 10 | 11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 12 | # to INSTALLED_APPS to enable admin documentation: 13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | (r'^admin/', include(admin.site.urls)), 17 | ) 18 | -------------------------------------------------------------------------------- /src/runtests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import django 4 | 5 | from django.conf import settings 6 | from django.test.utils import get_runner 7 | 8 | 9 | def runtests(): 10 | os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' 11 | django.setup() 12 | TestRunner = get_runner(settings) 13 | test_runner = TestRunner() 14 | failures = test_runner.run_tests(["django_fields.tests"]) 15 | sys.exit(bool(failures)) 16 | 17 | if __name__ == '__main__': 18 | runtests() 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | django18, 4 | django19, 5 | django110, 6 | django{master} 7 | 8 | [testenv] 9 | deps = 10 | django18: Django==1.8.16 11 | django19: Django==1.9.11 12 | django110: Django==1.10.3 13 | djangomaster: https://github.com/django/django/archive/master.tar.gz 14 | commands = python setup.py test 15 | -------------------------------------------------------------------------------- /virtualenv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Create a "virtual" Python installation 3 | """ 4 | 5 | # If you change the version here, change it in setup.py 6 | # and docs/conf.py as well. 7 | virtualenv_version = "1.6.4" 8 | 9 | import base64 10 | import sys 11 | import os 12 | import optparse 13 | import re 14 | import shutil 15 | import logging 16 | import tempfile 17 | import zlib 18 | import errno 19 | import distutils.sysconfig 20 | try: 21 | import subprocess 22 | except ImportError: 23 | if sys.version_info <= (2, 3): 24 | print('ERROR: %s' % sys.exc_info()[1]) 25 | print('ERROR: this script requires Python 2.4 or greater; or at least the subprocess module.') 26 | print('If you copy subprocess.py from a newer version of Python this script will probably work') 27 | sys.exit(101) 28 | else: 29 | raise 30 | try: 31 | set 32 | except NameError: 33 | from sets import Set as set 34 | try: 35 | basestring 36 | except NameError: 37 | basestring = str 38 | 39 | join = os.path.join 40 | py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) 41 | 42 | is_jython = sys.platform.startswith('java') 43 | is_pypy = hasattr(sys, 'pypy_version_info') 44 | is_win = (sys.platform == 'win32') 45 | abiflags = getattr(sys, 'abiflags', '') 46 | 47 | if is_pypy: 48 | expected_exe = 'pypy' 49 | elif is_jython: 50 | expected_exe = 'jython' 51 | else: 52 | expected_exe = 'python' 53 | 54 | 55 | REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', 56 | 'fnmatch', 'locale', 'encodings', 'codecs', 57 | 'stat', 'UserDict', 'readline', 'copy_reg', 'types', 58 | 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', 59 | 'zlib'] 60 | 61 | REQUIRED_FILES = ['lib-dynload', 'config'] 62 | 63 | majver, minver = sys.version_info[:2] 64 | if majver == 2: 65 | if minver >= 6: 66 | REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) 67 | if minver >= 7: 68 | REQUIRED_MODULES.extend(['_weakrefset']) 69 | if minver <= 3: 70 | REQUIRED_MODULES.extend(['sets', '__future__']) 71 | elif majver == 3: 72 | # Some extra modules are needed for Python 3, but different ones 73 | # for different versions. 74 | REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', 75 | '_weakrefset', 'copyreg', 'tempfile', 'random', 76 | '__future__', 'collections', 'keyword', 'tarfile', 77 | 'shutil', 'struct', 'copy']) 78 | if minver >= 2: 79 | REQUIRED_FILES[-1] = 'config-%s' % majver 80 | if minver == 3: 81 | # The whole list of 3.3 modules is reproduced below - the current 82 | # uncommented ones are required for 3.3 as of now, but more may be 83 | # added as 3.3 development continues. 84 | REQUIRED_MODULES.extend([ 85 | #"aifc", 86 | #"antigravity", 87 | #"argparse", 88 | #"ast", 89 | #"asynchat", 90 | #"asyncore", 91 | "base64", 92 | #"bdb", 93 | #"binhex", 94 | "bisect", 95 | #"calendar", 96 | #"cgi", 97 | #"cgitb", 98 | #"chunk", 99 | #"cmd", 100 | #"codeop", 101 | #"code", 102 | #"colorsys", 103 | #"_compat_pickle", 104 | #"compileall", 105 | #"concurrent", 106 | #"configparser", 107 | #"contextlib", 108 | #"cProfile", 109 | #"crypt", 110 | #"csv", 111 | #"ctypes", 112 | #"curses", 113 | #"datetime", 114 | #"dbm", 115 | #"decimal", 116 | #"difflib", 117 | #"dis", 118 | #"doctest", 119 | #"dummy_threading", 120 | "_dummy_thread", 121 | #"email", 122 | #"filecmp", 123 | #"fileinput", 124 | #"formatter", 125 | #"fractions", 126 | #"ftplib", 127 | #"functools", 128 | #"getopt", 129 | #"getpass", 130 | #"gettext", 131 | #"glob", 132 | #"gzip", 133 | "hashlib", 134 | "heapq", 135 | "hmac", 136 | #"html", 137 | #"http", 138 | #"idlelib", 139 | #"imaplib", 140 | #"imghdr", 141 | #"importlib", 142 | #"inspect", 143 | #"json", 144 | #"lib2to3", 145 | #"logging", 146 | #"macpath", 147 | #"macurl2path", 148 | #"mailbox", 149 | #"mailcap", 150 | #"_markupbase", 151 | #"mimetypes", 152 | #"modulefinder", 153 | #"multiprocessing", 154 | #"netrc", 155 | #"nntplib", 156 | #"nturl2path", 157 | #"numbers", 158 | #"opcode", 159 | #"optparse", 160 | #"os2emxpath", 161 | #"pdb", 162 | #"pickle", 163 | #"pickletools", 164 | #"pipes", 165 | #"pkgutil", 166 | #"platform", 167 | #"plat-linux2", 168 | #"plistlib", 169 | #"poplib", 170 | #"pprint", 171 | #"profile", 172 | #"pstats", 173 | #"pty", 174 | #"pyclbr", 175 | #"py_compile", 176 | #"pydoc_data", 177 | #"pydoc", 178 | #"_pyio", 179 | #"queue", 180 | #"quopri", 181 | "reprlib", 182 | "rlcompleter", 183 | #"runpy", 184 | #"sched", 185 | #"shelve", 186 | #"shlex", 187 | #"smtpd", 188 | #"smtplib", 189 | #"sndhdr", 190 | #"socket", 191 | #"socketserver", 192 | #"sqlite3", 193 | #"ssl", 194 | #"stringprep", 195 | #"string", 196 | #"_strptime", 197 | #"subprocess", 198 | #"sunau", 199 | #"symbol", 200 | #"symtable", 201 | #"sysconfig", 202 | #"tabnanny", 203 | #"telnetlib", 204 | #"test", 205 | #"textwrap", 206 | #"this", 207 | #"_threading_local", 208 | #"threading", 209 | #"timeit", 210 | #"tkinter", 211 | #"tokenize", 212 | #"token", 213 | #"traceback", 214 | #"trace", 215 | #"tty", 216 | #"turtledemo", 217 | #"turtle", 218 | #"unittest", 219 | #"urllib", 220 | #"uuid", 221 | #"uu", 222 | #"wave", 223 | "weakref", 224 | #"webbrowser", 225 | #"wsgiref", 226 | #"xdrlib", 227 | #"xml", 228 | #"xmlrpc", 229 | #"zipfile", 230 | ]) 231 | 232 | if is_pypy: 233 | # these are needed to correctly display the exceptions that may happen 234 | # during the bootstrap 235 | REQUIRED_MODULES.extend(['traceback', 'linecache']) 236 | 237 | class Logger(object): 238 | 239 | """ 240 | Logging object for use in command-line script. Allows ranges of 241 | levels, to avoid some redundancy of displayed information. 242 | """ 243 | 244 | DEBUG = logging.DEBUG 245 | INFO = logging.INFO 246 | NOTIFY = (logging.INFO+logging.WARN)/2 247 | WARN = WARNING = logging.WARN 248 | ERROR = logging.ERROR 249 | FATAL = logging.FATAL 250 | 251 | LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] 252 | 253 | def __init__(self, consumers): 254 | self.consumers = consumers 255 | self.indent = 0 256 | self.in_progress = None 257 | self.in_progress_hanging = False 258 | 259 | def debug(self, msg, *args, **kw): 260 | self.log(self.DEBUG, msg, *args, **kw) 261 | def info(self, msg, *args, **kw): 262 | self.log(self.INFO, msg, *args, **kw) 263 | def notify(self, msg, *args, **kw): 264 | self.log(self.NOTIFY, msg, *args, **kw) 265 | def warn(self, msg, *args, **kw): 266 | self.log(self.WARN, msg, *args, **kw) 267 | def error(self, msg, *args, **kw): 268 | self.log(self.WARN, msg, *args, **kw) 269 | def fatal(self, msg, *args, **kw): 270 | self.log(self.FATAL, msg, *args, **kw) 271 | def log(self, level, msg, *args, **kw): 272 | if args: 273 | if kw: 274 | raise TypeError( 275 | "You may give positional or keyword arguments, not both") 276 | args = args or kw 277 | rendered = None 278 | for consumer_level, consumer in self.consumers: 279 | if self.level_matches(level, consumer_level): 280 | if (self.in_progress_hanging 281 | and consumer in (sys.stdout, sys.stderr)): 282 | self.in_progress_hanging = False 283 | sys.stdout.write('\n') 284 | sys.stdout.flush() 285 | if rendered is None: 286 | if args: 287 | rendered = msg % args 288 | else: 289 | rendered = msg 290 | rendered = ' '*self.indent + rendered 291 | if hasattr(consumer, 'write'): 292 | consumer.write(rendered+'\n') 293 | else: 294 | consumer(rendered) 295 | 296 | def start_progress(self, msg): 297 | assert not self.in_progress, ( 298 | "Tried to start_progress(%r) while in_progress %r" 299 | % (msg, self.in_progress)) 300 | if self.level_matches(self.NOTIFY, self._stdout_level()): 301 | sys.stdout.write(msg) 302 | sys.stdout.flush() 303 | self.in_progress_hanging = True 304 | else: 305 | self.in_progress_hanging = False 306 | self.in_progress = msg 307 | 308 | def end_progress(self, msg='done.'): 309 | assert self.in_progress, ( 310 | "Tried to end_progress without start_progress") 311 | if self.stdout_level_matches(self.NOTIFY): 312 | if not self.in_progress_hanging: 313 | # Some message has been printed out since start_progress 314 | sys.stdout.write('...' + self.in_progress + msg + '\n') 315 | sys.stdout.flush() 316 | else: 317 | sys.stdout.write(msg + '\n') 318 | sys.stdout.flush() 319 | self.in_progress = None 320 | self.in_progress_hanging = False 321 | 322 | def show_progress(self): 323 | """If we are in a progress scope, and no log messages have been 324 | shown, write out another '.'""" 325 | if self.in_progress_hanging: 326 | sys.stdout.write('.') 327 | sys.stdout.flush() 328 | 329 | def stdout_level_matches(self, level): 330 | """Returns true if a message at this level will go to stdout""" 331 | return self.level_matches(level, self._stdout_level()) 332 | 333 | def _stdout_level(self): 334 | """Returns the level that stdout runs at""" 335 | for level, consumer in self.consumers: 336 | if consumer is sys.stdout: 337 | return level 338 | return self.FATAL 339 | 340 | def level_matches(self, level, consumer_level): 341 | """ 342 | >>> l = Logger([]) 343 | >>> l.level_matches(3, 4) 344 | False 345 | >>> l.level_matches(3, 2) 346 | True 347 | >>> l.level_matches(slice(None, 3), 3) 348 | False 349 | >>> l.level_matches(slice(None, 3), 2) 350 | True 351 | >>> l.level_matches(slice(1, 3), 1) 352 | True 353 | >>> l.level_matches(slice(2, 3), 1) 354 | False 355 | """ 356 | if isinstance(level, slice): 357 | start, stop = level.start, level.stop 358 | if start is not None and start > consumer_level: 359 | return False 360 | if stop is not None and stop <= consumer_level: 361 | return False 362 | return True 363 | else: 364 | return level >= consumer_level 365 | 366 | #@classmethod 367 | def level_for_integer(cls, level): 368 | levels = cls.LEVELS 369 | if level < 0: 370 | return levels[0] 371 | if level >= len(levels): 372 | return levels[-1] 373 | return levels[level] 374 | 375 | level_for_integer = classmethod(level_for_integer) 376 | 377 | # create a silent logger just to prevent this from being undefined 378 | # will be overridden with requested verbosity main() is called. 379 | logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) 380 | 381 | def mkdir(path): 382 | if not os.path.exists(path): 383 | logger.info('Creating %s', path) 384 | os.makedirs(path) 385 | else: 386 | logger.info('Directory %s already exists', path) 387 | 388 | def copyfileordir(src, dest): 389 | if os.path.isdir(src): 390 | shutil.copytree(src, dest, True) 391 | else: 392 | shutil.copy2(src, dest) 393 | 394 | def copyfile(src, dest, symlink=True): 395 | if not os.path.exists(src): 396 | # Some bad symlink in the src 397 | logger.warn('Cannot find file %s (bad symlink)', src) 398 | return 399 | if os.path.exists(dest): 400 | logger.debug('File %s already exists', dest) 401 | return 402 | if not os.path.exists(os.path.dirname(dest)): 403 | logger.info('Creating parent directories for %s' % os.path.dirname(dest)) 404 | os.makedirs(os.path.dirname(dest)) 405 | if not os.path.islink(src): 406 | srcpath = os.path.abspath(src) 407 | else: 408 | srcpath = os.readlink(src) 409 | if symlink and hasattr(os, 'symlink'): 410 | logger.info('Symlinking %s', dest) 411 | try: 412 | os.symlink(srcpath, dest) 413 | except (OSError, NotImplementedError): 414 | logger.info('Symlinking failed, copying to %s', dest) 415 | copyfileordir(src, dest) 416 | else: 417 | logger.info('Copying to %s', dest) 418 | copyfileordir(src, dest) 419 | 420 | def writefile(dest, content, overwrite=True): 421 | if not os.path.exists(dest): 422 | logger.info('Writing %s', dest) 423 | f = open(dest, 'wb') 424 | f.write(content.encode('utf-8')) 425 | f.close() 426 | return 427 | else: 428 | f = open(dest, 'rb') 429 | c = f.read() 430 | f.close() 431 | if c != content: 432 | if not overwrite: 433 | logger.notify('File %s exists with different content; not overwriting', dest) 434 | return 435 | logger.notify('Overwriting %s with new content', dest) 436 | f = open(dest, 'wb') 437 | f.write(content.encode('utf-8')) 438 | f.close() 439 | else: 440 | logger.info('Content %s already in place', dest) 441 | 442 | def rmtree(dir): 443 | if os.path.exists(dir): 444 | logger.notify('Deleting tree %s', dir) 445 | shutil.rmtree(dir) 446 | else: 447 | logger.info('Do not need to delete %s; already gone', dir) 448 | 449 | def make_exe(fn): 450 | if hasattr(os, 'chmod'): 451 | oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 452 | newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 453 | os.chmod(fn, newmode) 454 | logger.info('Changed mode of %s to %s', fn, oct(newmode)) 455 | 456 | def _find_file(filename, dirs): 457 | for dir in dirs: 458 | if os.path.exists(join(dir, filename)): 459 | return join(dir, filename) 460 | return filename 461 | 462 | def _install_req(py_executable, unzip=False, distribute=False, 463 | search_dirs=None, never_download=False): 464 | 465 | if search_dirs is None: 466 | search_dirs = file_search_dirs() 467 | 468 | if not distribute: 469 | setup_fn = 'setuptools-0.6c11-py%s.egg' % sys.version[:3] 470 | project_name = 'setuptools' 471 | bootstrap_script = EZ_SETUP_PY 472 | source = None 473 | else: 474 | setup_fn = None 475 | source = 'distribute-0.6.19.tar.gz' 476 | project_name = 'distribute' 477 | bootstrap_script = DISTRIBUTE_SETUP_PY 478 | 479 | # If we are running under -p, we need to remove the current 480 | # directory from sys.path temporarily here, so that we 481 | # definitely get the pkg_resources from the site directory of 482 | # the interpreter we are running under, not the one 483 | # virtualenv.py is installed under (which might lead to py2/py3 484 | # incompatibility issues) 485 | _prev_sys_path = sys.path 486 | if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): 487 | sys.path = sys.path[1:] 488 | 489 | try: 490 | try: 491 | # check if the global Python has distribute installed or plain 492 | # setuptools 493 | import pkg_resources 494 | if not hasattr(pkg_resources, '_distribute'): 495 | location = os.path.dirname(pkg_resources.__file__) 496 | logger.notify("A globally installed setuptools was found (in %s)" % location) 497 | logger.notify("Use the --no-site-packages option to use distribute in " 498 | "the virtualenv.") 499 | except ImportError: 500 | pass 501 | finally: 502 | sys.path = _prev_sys_path 503 | 504 | if setup_fn is not None: 505 | setup_fn = _find_file(setup_fn, search_dirs) 506 | 507 | if source is not None: 508 | source = _find_file(source, search_dirs) 509 | 510 | if is_jython and os._name == 'nt': 511 | # Jython's .bat sys.executable can't handle a command line 512 | # argument with newlines 513 | fd, ez_setup = tempfile.mkstemp('.py') 514 | os.write(fd, bootstrap_script) 515 | os.close(fd) 516 | cmd = [py_executable, ez_setup] 517 | else: 518 | cmd = [py_executable, '-c', bootstrap_script] 519 | if unzip: 520 | cmd.append('--always-unzip') 521 | env = {} 522 | remove_from_env = [] 523 | if logger.stdout_level_matches(logger.DEBUG): 524 | cmd.append('-v') 525 | 526 | old_chdir = os.getcwd() 527 | if setup_fn is not None and os.path.exists(setup_fn): 528 | logger.info('Using existing %s egg: %s' % (project_name, setup_fn)) 529 | cmd.append(setup_fn) 530 | if os.environ.get('PYTHONPATH'): 531 | env['PYTHONPATH'] = setup_fn + os.path.pathsep + os.environ['PYTHONPATH'] 532 | else: 533 | env['PYTHONPATH'] = setup_fn 534 | else: 535 | # the source is found, let's chdir 536 | if source is not None and os.path.exists(source): 537 | logger.info('Using existing %s egg: %s' % (project_name, source)) 538 | os.chdir(os.path.dirname(source)) 539 | # in this case, we want to be sure that PYTHONPATH is unset (not 540 | # just empty, really unset), else CPython tries to import the 541 | # site.py that it's in virtualenv_support 542 | remove_from_env.append('PYTHONPATH') 543 | else: 544 | if never_download: 545 | logger.fatal("Can't find any local distributions of %s to install " 546 | "and --never-download is set. Either re-run virtualenv " 547 | "without the --never-download option, or place a %s " 548 | "distribution (%s) in one of these " 549 | "locations: %r" % (project_name, project_name, 550 | setup_fn or source, 551 | search_dirs)) 552 | sys.exit(1) 553 | 554 | logger.info('No %s egg found; downloading' % project_name) 555 | cmd.extend(['--always-copy', '-U', project_name]) 556 | logger.start_progress('Installing %s...' % project_name) 557 | logger.indent += 2 558 | cwd = None 559 | if project_name == 'distribute': 560 | env['DONT_PATCH_SETUPTOOLS'] = 'true' 561 | 562 | def _filter_ez_setup(line): 563 | return filter_ez_setup(line, project_name) 564 | 565 | if not os.access(os.getcwd(), os.W_OK): 566 | cwd = tempfile.mkdtemp() 567 | if source is not None and os.path.exists(source): 568 | # the current working dir is hostile, let's copy the 569 | # tarball to a temp dir 570 | target = os.path.join(cwd, os.path.split(source)[-1]) 571 | shutil.copy(source, target) 572 | try: 573 | call_subprocess(cmd, show_stdout=False, 574 | filter_stdout=_filter_ez_setup, 575 | extra_env=env, 576 | remove_from_env=remove_from_env, 577 | cwd=cwd) 578 | finally: 579 | logger.indent -= 2 580 | logger.end_progress() 581 | if os.getcwd() != old_chdir: 582 | os.chdir(old_chdir) 583 | if is_jython and os._name == 'nt': 584 | os.remove(ez_setup) 585 | 586 | def file_search_dirs(): 587 | here = os.path.dirname(os.path.abspath(__file__)) 588 | dirs = ['.', here, 589 | join(here, 'virtualenv_support')] 590 | if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': 591 | # Probably some boot script; just in case virtualenv is installed... 592 | try: 593 | import virtualenv 594 | except ImportError: 595 | pass 596 | else: 597 | dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) 598 | return [d for d in dirs if os.path.isdir(d)] 599 | 600 | def install_setuptools(py_executable, unzip=False, 601 | search_dirs=None, never_download=False): 602 | _install_req(py_executable, unzip, 603 | search_dirs=search_dirs, never_download=never_download) 604 | 605 | def install_distribute(py_executable, unzip=False, 606 | search_dirs=None, never_download=False): 607 | _install_req(py_executable, unzip, distribute=True, 608 | search_dirs=search_dirs, never_download=never_download) 609 | 610 | _pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I) 611 | def install_pip(py_executable, search_dirs=None, never_download=False): 612 | if search_dirs is None: 613 | search_dirs = file_search_dirs() 614 | 615 | filenames = [] 616 | for dir in search_dirs: 617 | filenames.extend([join(dir, fn) for fn in os.listdir(dir) 618 | if _pip_re.search(fn)]) 619 | filenames = [(os.path.basename(filename).lower(), i, filename) for i, filename in enumerate(filenames)] 620 | filenames.sort() 621 | filenames = [filename for basename, i, filename in filenames] 622 | if not filenames: 623 | filename = 'pip' 624 | else: 625 | filename = filenames[-1] 626 | easy_install_script = 'easy_install' 627 | if sys.platform == 'win32': 628 | easy_install_script = 'easy_install-script.py' 629 | cmd = [py_executable, join(os.path.dirname(py_executable), easy_install_script), filename] 630 | if filename == 'pip': 631 | if never_download: 632 | logger.fatal("Can't find any local distributions of pip to install " 633 | "and --never-download is set. Either re-run virtualenv " 634 | "without the --never-download option, or place a pip " 635 | "source distribution (zip/tar.gz/tar.bz2) in one of these " 636 | "locations: %r" % search_dirs) 637 | sys.exit(1) 638 | logger.info('Installing pip from network...') 639 | else: 640 | logger.info('Installing existing %s distribution: %s' % ( 641 | os.path.basename(filename), filename)) 642 | logger.start_progress('Installing pip...') 643 | logger.indent += 2 644 | def _filter_setup(line): 645 | return filter_ez_setup(line, 'pip') 646 | try: 647 | call_subprocess(cmd, show_stdout=False, 648 | filter_stdout=_filter_setup) 649 | finally: 650 | logger.indent -= 2 651 | logger.end_progress() 652 | 653 | def filter_ez_setup(line, project_name='setuptools'): 654 | if not line.strip(): 655 | return Logger.DEBUG 656 | if project_name == 'distribute': 657 | for prefix in ('Extracting', 'Now working', 'Installing', 'Before', 658 | 'Scanning', 'Setuptools', 'Egg', 'Already', 659 | 'running', 'writing', 'reading', 'installing', 660 | 'creating', 'copying', 'byte-compiling', 'removing', 661 | 'Processing'): 662 | if line.startswith(prefix): 663 | return Logger.DEBUG 664 | return Logger.DEBUG 665 | for prefix in ['Reading ', 'Best match', 'Processing setuptools', 666 | 'Copying setuptools', 'Adding setuptools', 667 | 'Installing ', 'Installed ']: 668 | if line.startswith(prefix): 669 | return Logger.DEBUG 670 | return Logger.INFO 671 | 672 | def main(): 673 | parser = optparse.OptionParser( 674 | version=virtualenv_version, 675 | usage="%prog [OPTIONS] DEST_DIR") 676 | 677 | parser.add_option( 678 | '-v', '--verbose', 679 | action='count', 680 | dest='verbose', 681 | default=0, 682 | help="Increase verbosity") 683 | 684 | parser.add_option( 685 | '-q', '--quiet', 686 | action='count', 687 | dest='quiet', 688 | default=0, 689 | help='Decrease verbosity') 690 | 691 | parser.add_option( 692 | '-p', '--python', 693 | dest='python', 694 | metavar='PYTHON_EXE', 695 | help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' 696 | 'interpreter to create the new environment. The default is the interpreter that ' 697 | 'virtualenv was installed with (%s)' % sys.executable) 698 | 699 | parser.add_option( 700 | '--clear', 701 | dest='clear', 702 | action='store_true', 703 | help="Clear out the non-root install and start from scratch") 704 | 705 | parser.add_option( 706 | '--no-site-packages', 707 | dest='no_site_packages', 708 | action='store_true', 709 | help="Don't give access to the global site-packages dir to the " 710 | "virtual environment") 711 | 712 | parser.add_option( 713 | '--unzip-setuptools', 714 | dest='unzip_setuptools', 715 | action='store_true', 716 | help="Unzip Setuptools or Distribute when installing it") 717 | 718 | parser.add_option( 719 | '--relocatable', 720 | dest='relocatable', 721 | action='store_true', 722 | help='Make an EXISTING virtualenv environment relocatable. ' 723 | 'This fixes up scripts and makes all .pth files relative') 724 | 725 | parser.add_option( 726 | '--distribute', 727 | dest='use_distribute', 728 | action='store_true', 729 | help='Use Distribute instead of Setuptools. Set environ variable ' 730 | 'VIRTUALENV_USE_DISTRIBUTE to make it the default ') 731 | 732 | default_search_dirs = file_search_dirs() 733 | parser.add_option( 734 | '--extra-search-dir', 735 | dest="search_dirs", 736 | action="append", 737 | default=default_search_dirs, 738 | help="Directory to look for setuptools/distribute/pip distributions in. " 739 | "You can add any number of additional --extra-search-dir paths.") 740 | 741 | parser.add_option( 742 | '--never-download', 743 | dest="never_download", 744 | action="store_true", 745 | help="Never download anything from the network. Instead, virtualenv will fail " 746 | "if local distributions of setuptools/distribute/pip are not present.") 747 | 748 | parser.add_option( 749 | '--prompt=', 750 | dest='prompt', 751 | help='Provides an alternative prompt prefix for this environment') 752 | 753 | if 'extend_parser' in globals(): 754 | extend_parser(parser) 755 | 756 | options, args = parser.parse_args() 757 | 758 | global logger 759 | 760 | if 'adjust_options' in globals(): 761 | adjust_options(options, args) 762 | 763 | verbosity = options.verbose - options.quiet 764 | logger = Logger([(Logger.level_for_integer(2-verbosity), sys.stdout)]) 765 | 766 | if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): 767 | env = os.environ.copy() 768 | interpreter = resolve_interpreter(options.python) 769 | if interpreter == sys.executable: 770 | logger.warn('Already using interpreter %s' % interpreter) 771 | else: 772 | logger.notify('Running virtualenv with interpreter %s' % interpreter) 773 | env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' 774 | file = __file__ 775 | if file.endswith('.pyc'): 776 | file = file[:-1] 777 | popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) 778 | raise SystemExit(popen.wait()) 779 | 780 | if not args: 781 | print('You must provide a DEST_DIR') 782 | parser.print_help() 783 | sys.exit(2) 784 | if len(args) > 1: 785 | print('There must be only one argument: DEST_DIR (you gave %s)' % ( 786 | ' '.join(args))) 787 | parser.print_help() 788 | sys.exit(2) 789 | 790 | home_dir = args[0] 791 | 792 | if os.environ.get('WORKING_ENV'): 793 | logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') 794 | logger.fatal('Please deactivate your workingenv, then re-run this script') 795 | sys.exit(3) 796 | 797 | if 'PYTHONHOME' in os.environ: 798 | logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') 799 | del os.environ['PYTHONHOME'] 800 | 801 | if options.relocatable: 802 | make_environment_relocatable(home_dir) 803 | return 804 | 805 | create_environment(home_dir, site_packages=not options.no_site_packages, clear=options.clear, 806 | unzip_setuptools=options.unzip_setuptools, 807 | use_distribute=options.use_distribute or majver > 2, 808 | prompt=options.prompt, 809 | search_dirs=options.search_dirs, 810 | never_download=options.never_download) 811 | if 'after_install' in globals(): 812 | after_install(options, home_dir) 813 | 814 | def call_subprocess(cmd, show_stdout=True, 815 | filter_stdout=None, cwd=None, 816 | raise_on_returncode=True, extra_env=None, 817 | remove_from_env=None): 818 | cmd_parts = [] 819 | for part in cmd: 820 | if len(part) > 45: 821 | part = part[:20]+"..."+part[-20:] 822 | if ' ' in part or '\n' in part or '"' in part or "'" in part: 823 | part = '"%s"' % part.replace('"', '\\"') 824 | cmd_parts.append(part) 825 | cmd_desc = ' '.join(cmd_parts) 826 | if show_stdout: 827 | stdout = None 828 | else: 829 | stdout = subprocess.PIPE 830 | logger.debug("Running command %s" % cmd_desc) 831 | if extra_env or remove_from_env: 832 | env = os.environ.copy() 833 | if extra_env: 834 | env.update(extra_env) 835 | if remove_from_env: 836 | for varname in remove_from_env: 837 | env.pop(varname, None) 838 | else: 839 | env = None 840 | try: 841 | proc = subprocess.Popen( 842 | cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, 843 | cwd=cwd, env=env) 844 | except Exception: 845 | e = sys.exc_info()[1] 846 | logger.fatal( 847 | "Error %s while executing command %s" % (e, cmd_desc)) 848 | raise 849 | all_output = [] 850 | if stdout is not None: 851 | stdout = proc.stdout 852 | encoding = sys.getdefaultencoding() 853 | while 1: 854 | line = stdout.readline().decode(encoding) 855 | if not line: 856 | break 857 | line = line.rstrip() 858 | all_output.append(line) 859 | if filter_stdout: 860 | level = filter_stdout(line) 861 | if isinstance(level, tuple): 862 | level, line = level 863 | logger.log(level, line) 864 | if not logger.stdout_level_matches(level): 865 | logger.show_progress() 866 | else: 867 | logger.info(line) 868 | else: 869 | proc.communicate() 870 | proc.wait() 871 | if proc.returncode: 872 | if raise_on_returncode: 873 | if all_output: 874 | logger.notify('Complete output from command %s:' % cmd_desc) 875 | logger.notify('\n'.join(all_output) + '\n----------------------------------------') 876 | raise OSError( 877 | "Command %s failed with error code %s" 878 | % (cmd_desc, proc.returncode)) 879 | else: 880 | logger.warn( 881 | "Command %s had error code %s" 882 | % (cmd_desc, proc.returncode)) 883 | 884 | 885 | def create_environment(home_dir, site_packages=True, clear=False, 886 | unzip_setuptools=False, use_distribute=False, 887 | prompt=None, search_dirs=None, never_download=False): 888 | """ 889 | Creates a new environment in ``home_dir``. 890 | 891 | If ``site_packages`` is true (the default) then the global 892 | ``site-packages/`` directory will be on the path. 893 | 894 | If ``clear`` is true (default False) then the environment will 895 | first be cleared. 896 | """ 897 | home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) 898 | 899 | py_executable = os.path.abspath(install_python( 900 | home_dir, lib_dir, inc_dir, bin_dir, 901 | site_packages=site_packages, clear=clear)) 902 | 903 | install_distutils(home_dir) 904 | 905 | if use_distribute or os.environ.get('VIRTUALENV_USE_DISTRIBUTE'): 906 | install_distribute(py_executable, unzip=unzip_setuptools, 907 | search_dirs=search_dirs, never_download=never_download) 908 | else: 909 | install_setuptools(py_executable, unzip=unzip_setuptools, 910 | search_dirs=search_dirs, never_download=never_download) 911 | 912 | install_pip(py_executable, search_dirs=search_dirs, never_download=never_download) 913 | 914 | install_activate(home_dir, bin_dir, prompt) 915 | 916 | def path_locations(home_dir): 917 | """Return the path locations for the environment (where libraries are, 918 | where scripts go, etc)""" 919 | # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its 920 | # prefix arg is broken: http://bugs.python.org/issue3386 921 | if sys.platform == 'win32': 922 | # Windows has lots of problems with executables with spaces in 923 | # the name; this function will remove them (using the ~1 924 | # format): 925 | mkdir(home_dir) 926 | if ' ' in home_dir: 927 | try: 928 | import win32api 929 | except ImportError: 930 | print('Error: the path "%s" has a space in it' % home_dir) 931 | print('To handle these kinds of paths, the win32api module must be installed:') 932 | print(' http://sourceforge.net/projects/pywin32/') 933 | sys.exit(3) 934 | home_dir = win32api.GetShortPathName(home_dir) 935 | lib_dir = join(home_dir, 'Lib') 936 | inc_dir = join(home_dir, 'Include') 937 | bin_dir = join(home_dir, 'Scripts') 938 | elif is_jython: 939 | lib_dir = join(home_dir, 'Lib') 940 | inc_dir = join(home_dir, 'Include') 941 | bin_dir = join(home_dir, 'bin') 942 | elif is_pypy: 943 | lib_dir = home_dir 944 | inc_dir = join(home_dir, 'include') 945 | bin_dir = join(home_dir, 'bin') 946 | else: 947 | lib_dir = join(home_dir, 'lib', py_version) 948 | inc_dir = join(home_dir, 'include', py_version + abiflags) 949 | bin_dir = join(home_dir, 'bin') 950 | return home_dir, lib_dir, inc_dir, bin_dir 951 | 952 | 953 | def change_prefix(filename, dst_prefix): 954 | prefixes = [sys.prefix] 955 | 956 | if sys.platform == "darwin": 957 | prefixes.extend(( 958 | os.path.join("/Library/Python", sys.version[:3], "site-packages"), 959 | os.path.join(sys.prefix, "Extras", "lib", "python"), 960 | os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"))) 961 | 962 | if hasattr(sys, 'real_prefix'): 963 | prefixes.append(sys.real_prefix) 964 | prefixes = list(map(os.path.abspath, prefixes)) 965 | filename = os.path.abspath(filename) 966 | for src_prefix in prefixes: 967 | if filename.startswith(src_prefix): 968 | _, relpath = filename.split(src_prefix, 1) 969 | assert relpath[0] == os.sep 970 | relpath = relpath[1:] 971 | return join(dst_prefix, relpath) 972 | assert False, "Filename %s does not start with any of these prefixes: %s" % \ 973 | (filename, prefixes) 974 | 975 | def copy_required_modules(dst_prefix): 976 | import imp 977 | for modname in REQUIRED_MODULES: 978 | if modname in sys.builtin_module_names: 979 | logger.info("Ignoring built-in bootstrap module: %s" % modname) 980 | continue 981 | try: 982 | f, filename, _ = imp.find_module(modname) 983 | except ImportError: 984 | logger.info("Cannot import bootstrap module: %s" % modname) 985 | else: 986 | if f is not None: 987 | f.close() 988 | dst_filename = change_prefix(filename, dst_prefix) 989 | copyfile(filename, dst_filename) 990 | if filename.endswith('.pyc'): 991 | pyfile = filename[:-1] 992 | if os.path.exists(pyfile): 993 | copyfile(pyfile, dst_filename[:-1]) 994 | 995 | 996 | def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): 997 | """Install just the base environment, no distutils patches etc""" 998 | if sys.executable.startswith(bin_dir): 999 | print('Please use the *system* python to run this script') 1000 | return 1001 | 1002 | if clear: 1003 | rmtree(lib_dir) 1004 | ## FIXME: why not delete it? 1005 | ## Maybe it should delete everything with #!/path/to/venv/python in it 1006 | logger.notify('Not deleting %s', bin_dir) 1007 | 1008 | if hasattr(sys, 'real_prefix'): 1009 | logger.notify('Using real prefix %r' % sys.real_prefix) 1010 | prefix = sys.real_prefix 1011 | else: 1012 | prefix = sys.prefix 1013 | mkdir(lib_dir) 1014 | fix_lib64(lib_dir) 1015 | fix_local_scheme(home_dir) 1016 | stdlib_dirs = [os.path.dirname(os.__file__)] 1017 | if sys.platform == 'win32': 1018 | stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) 1019 | elif sys.platform == 'darwin': 1020 | stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) 1021 | if hasattr(os, 'symlink'): 1022 | logger.info('Symlinking Python bootstrap modules') 1023 | else: 1024 | logger.info('Copying Python bootstrap modules') 1025 | logger.indent += 2 1026 | try: 1027 | # copy required files... 1028 | for stdlib_dir in stdlib_dirs: 1029 | if not os.path.isdir(stdlib_dir): 1030 | continue 1031 | for fn in os.listdir(stdlib_dir): 1032 | bn = os.path.splitext(fn)[0] 1033 | if fn != 'site-packages' and bn in REQUIRED_FILES: 1034 | copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) 1035 | # ...and modules 1036 | copy_required_modules(home_dir) 1037 | finally: 1038 | logger.indent -= 2 1039 | mkdir(join(lib_dir, 'site-packages')) 1040 | import site 1041 | site_filename = site.__file__ 1042 | if site_filename.endswith('.pyc'): 1043 | site_filename = site_filename[:-1] 1044 | elif site_filename.endswith('$py.class'): 1045 | site_filename = site_filename.replace('$py.class', '.py') 1046 | site_filename_dst = change_prefix(site_filename, home_dir) 1047 | site_dir = os.path.dirname(site_filename_dst) 1048 | writefile(site_filename_dst, SITE_PY) 1049 | writefile(join(site_dir, 'orig-prefix.txt'), prefix) 1050 | site_packages_filename = join(site_dir, 'no-global-site-packages.txt') 1051 | if not site_packages: 1052 | writefile(site_packages_filename, '') 1053 | else: 1054 | if os.path.exists(site_packages_filename): 1055 | logger.info('Deleting %s' % site_packages_filename) 1056 | os.unlink(site_packages_filename) 1057 | 1058 | if is_pypy or is_win: 1059 | stdinc_dir = join(prefix, 'include') 1060 | else: 1061 | stdinc_dir = join(prefix, 'include', py_version + abiflags) 1062 | if os.path.exists(stdinc_dir): 1063 | copyfile(stdinc_dir, inc_dir) 1064 | else: 1065 | logger.debug('No include dir %s' % stdinc_dir) 1066 | 1067 | # pypy never uses exec_prefix, just ignore it 1068 | if sys.exec_prefix != prefix and not is_pypy: 1069 | if sys.platform == 'win32': 1070 | exec_dir = join(sys.exec_prefix, 'lib') 1071 | elif is_jython: 1072 | exec_dir = join(sys.exec_prefix, 'Lib') 1073 | else: 1074 | exec_dir = join(sys.exec_prefix, 'lib', py_version) 1075 | for fn in os.listdir(exec_dir): 1076 | copyfile(join(exec_dir, fn), join(lib_dir, fn)) 1077 | 1078 | if is_jython: 1079 | # Jython has either jython-dev.jar and javalib/ dir, or just 1080 | # jython.jar 1081 | for name in 'jython-dev.jar', 'javalib', 'jython.jar': 1082 | src = join(prefix, name) 1083 | if os.path.exists(src): 1084 | copyfile(src, join(home_dir, name)) 1085 | # XXX: registry should always exist after Jython 2.5rc1 1086 | src = join(prefix, 'registry') 1087 | if os.path.exists(src): 1088 | copyfile(src, join(home_dir, 'registry'), symlink=False) 1089 | copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), 1090 | symlink=False) 1091 | 1092 | mkdir(bin_dir) 1093 | py_executable = join(bin_dir, os.path.basename(sys.executable)) 1094 | if 'Python.framework' in prefix: 1095 | if re.search(r'/Python(?:-32|-64)*$', py_executable): 1096 | # The name of the python executable is not quite what 1097 | # we want, rename it. 1098 | py_executable = os.path.join( 1099 | os.path.dirname(py_executable), 'python') 1100 | 1101 | logger.notify('New %s executable in %s', expected_exe, py_executable) 1102 | if sys.executable != py_executable: 1103 | ## FIXME: could I just hard link? 1104 | executable = sys.executable 1105 | if sys.platform == 'cygwin' and os.path.exists(executable + '.exe'): 1106 | # Cygwin misreports sys.executable sometimes 1107 | executable += '.exe' 1108 | py_executable += '.exe' 1109 | logger.info('Executable actually exists in %s' % executable) 1110 | shutil.copyfile(executable, py_executable) 1111 | make_exe(py_executable) 1112 | if sys.platform == 'win32' or sys.platform == 'cygwin': 1113 | pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') 1114 | if os.path.exists(pythonw): 1115 | logger.info('Also created pythonw.exe') 1116 | shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) 1117 | if is_pypy: 1118 | # make a symlink python --> pypy-c 1119 | python_executable = os.path.join(os.path.dirname(py_executable), 'python') 1120 | logger.info('Also created executable %s' % python_executable) 1121 | copyfile(py_executable, python_executable) 1122 | 1123 | if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: 1124 | secondary_exe = os.path.join(os.path.dirname(py_executable), 1125 | expected_exe) 1126 | py_executable_ext = os.path.splitext(py_executable)[1] 1127 | if py_executable_ext == '.exe': 1128 | # python2.4 gives an extension of '.4' :P 1129 | secondary_exe += py_executable_ext 1130 | if os.path.exists(secondary_exe): 1131 | logger.warn('Not overwriting existing %s script %s (you must use %s)' 1132 | % (expected_exe, secondary_exe, py_executable)) 1133 | else: 1134 | logger.notify('Also creating executable in %s' % secondary_exe) 1135 | shutil.copyfile(sys.executable, secondary_exe) 1136 | make_exe(secondary_exe) 1137 | 1138 | if 'Python.framework' in prefix: 1139 | logger.debug('MacOSX Python framework detected') 1140 | 1141 | # Make sure we use the the embedded interpreter inside 1142 | # the framework, even if sys.executable points to 1143 | # the stub executable in ${sys.prefix}/bin 1144 | # See http://groups.google.com/group/python-virtualenv/ 1145 | # browse_thread/thread/17cab2f85da75951 1146 | original_python = os.path.join( 1147 | prefix, 'Resources/Python.app/Contents/MacOS/Python') 1148 | shutil.copy(original_python, py_executable) 1149 | 1150 | # Copy the framework's dylib into the virtual 1151 | # environment 1152 | virtual_lib = os.path.join(home_dir, '.Python') 1153 | 1154 | if os.path.exists(virtual_lib): 1155 | os.unlink(virtual_lib) 1156 | copyfile( 1157 | os.path.join(prefix, 'Python'), 1158 | virtual_lib) 1159 | 1160 | # And then change the install_name of the copied python executable 1161 | try: 1162 | call_subprocess( 1163 | ["install_name_tool", "-change", 1164 | os.path.join(prefix, 'Python'), 1165 | '@executable_path/../.Python', 1166 | py_executable]) 1167 | except: 1168 | logger.fatal( 1169 | "Could not call install_name_tool -- you must have Apple's development tools installed") 1170 | raise 1171 | 1172 | # Some tools depend on pythonX.Y being present 1173 | py_executable_version = '%s.%s' % ( 1174 | sys.version_info[0], sys.version_info[1]) 1175 | if not py_executable.endswith(py_executable_version): 1176 | # symlinking pythonX.Y > python 1177 | pth = py_executable + '%s.%s' % ( 1178 | sys.version_info[0], sys.version_info[1]) 1179 | if os.path.exists(pth): 1180 | os.unlink(pth) 1181 | os.symlink('python', pth) 1182 | else: 1183 | # reverse symlinking python -> pythonX.Y (with --python) 1184 | pth = join(bin_dir, 'python') 1185 | if os.path.exists(pth): 1186 | os.unlink(pth) 1187 | os.symlink(os.path.basename(py_executable), pth) 1188 | 1189 | if sys.platform == 'win32' and ' ' in py_executable: 1190 | # There's a bug with subprocess on Windows when using a first 1191 | # argument that has a space in it. Instead we have to quote 1192 | # the value: 1193 | py_executable = '"%s"' % py_executable 1194 | cmd = [py_executable, '-c', 'import sys; print(sys.prefix)'] 1195 | logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) 1196 | try: 1197 | proc = subprocess.Popen(cmd, 1198 | stdout=subprocess.PIPE) 1199 | proc_stdout, proc_stderr = proc.communicate() 1200 | except OSError: 1201 | e = sys.exc_info()[1] 1202 | if e.errno == errno.EACCES: 1203 | logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) 1204 | sys.exit(100) 1205 | else: 1206 | raise e 1207 | 1208 | proc_stdout = proc_stdout.strip().decode(sys.getdefaultencoding()) 1209 | proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) 1210 | if proc_stdout != os.path.normcase(os.path.abspath(home_dir)): 1211 | logger.fatal( 1212 | 'ERROR: The executable %s is not functioning' % py_executable) 1213 | logger.fatal( 1214 | 'ERROR: It thinks sys.prefix is %r (should be %r)' 1215 | % (proc_stdout, os.path.normcase(os.path.abspath(home_dir)))) 1216 | logger.fatal( 1217 | 'ERROR: virtualenv is not compatible with this system or executable') 1218 | if sys.platform == 'win32': 1219 | logger.fatal( 1220 | 'Note: some Windows users have reported this error when they installed Python for "Only this user". The problem may be resolvable if you install Python "For all users". (See https://bugs.launchpad.net/virtualenv/+bug/352844)') 1221 | sys.exit(100) 1222 | else: 1223 | logger.info('Got sys.prefix result: %r' % proc_stdout) 1224 | 1225 | pydistutils = os.path.expanduser('~/.pydistutils.cfg') 1226 | if os.path.exists(pydistutils): 1227 | logger.notify('Please make sure you remove any previous custom paths from ' 1228 | 'your %s file.' % pydistutils) 1229 | ## FIXME: really this should be calculated earlier 1230 | return py_executable 1231 | 1232 | def install_activate(home_dir, bin_dir, prompt=None): 1233 | if sys.platform == 'win32' or is_jython and os._name == 'nt': 1234 | files = {'activate.bat': ACTIVATE_BAT, 1235 | 'deactivate.bat': DEACTIVATE_BAT} 1236 | if os.environ.get('OS') == 'Windows_NT' and os.environ.get('OSTYPE') == 'cygwin': 1237 | files['activate'] = ACTIVATE_SH 1238 | else: 1239 | files = {'activate': ACTIVATE_SH} 1240 | 1241 | # suppling activate.fish in addition to, not instead of, the 1242 | # bash script support. 1243 | files['activate.fish'] = ACTIVATE_FISH 1244 | 1245 | # same for csh/tcsh support... 1246 | files['activate.csh'] = ACTIVATE_CSH 1247 | 1248 | 1249 | 1250 | files['activate_this.py'] = ACTIVATE_THIS 1251 | vname = os.path.basename(os.path.abspath(home_dir)) 1252 | for name, content in files.items(): 1253 | content = content.replace('__VIRTUAL_PROMPT__', prompt or '') 1254 | content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) 1255 | content = content.replace('__VIRTUAL_ENV__', os.path.abspath(home_dir)) 1256 | content = content.replace('__VIRTUAL_NAME__', vname) 1257 | content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) 1258 | writefile(os.path.join(bin_dir, name), content) 1259 | 1260 | def install_distutils(home_dir): 1261 | distutils_path = change_prefix(distutils.__path__[0], home_dir) 1262 | mkdir(distutils_path) 1263 | ## FIXME: maybe this prefix setting should only be put in place if 1264 | ## there's a local distutils.cfg with a prefix setting? 1265 | home_dir = os.path.abspath(home_dir) 1266 | ## FIXME: this is breaking things, removing for now: 1267 | #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir 1268 | writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) 1269 | writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) 1270 | 1271 | def fix_local_scheme(home_dir): 1272 | """ 1273 | Platforms that use the "posix_local" install scheme (like Ubuntu with 1274 | Python 2.7) need to be given an additional "local" location, sigh. 1275 | """ 1276 | try: 1277 | import sysconfig 1278 | except ImportError: 1279 | pass 1280 | else: 1281 | if sysconfig._get_default_scheme() == 'posix_local': 1282 | local_path = os.path.join(home_dir, 'local') 1283 | if not os.path.exists(local_path): 1284 | os.symlink(os.path.abspath(home_dir), local_path) 1285 | 1286 | def fix_lib64(lib_dir): 1287 | """ 1288 | Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y 1289 | instead of lib/pythonX.Y. If this is such a platform we'll just create a 1290 | symlink so lib64 points to lib 1291 | """ 1292 | if [p for p in distutils.sysconfig.get_config_vars().values() 1293 | if isinstance(p, basestring) and 'lib64' in p]: 1294 | logger.debug('This system uses lib64; symlinking lib64 to lib') 1295 | assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( 1296 | "Unexpected python lib dir: %r" % lib_dir) 1297 | lib_parent = os.path.dirname(lib_dir) 1298 | assert os.path.basename(lib_parent) == 'lib', ( 1299 | "Unexpected parent dir: %r" % lib_parent) 1300 | copyfile(lib_parent, os.path.join(os.path.dirname(lib_parent), 'lib64')) 1301 | 1302 | def resolve_interpreter(exe): 1303 | """ 1304 | If the executable given isn't an absolute path, search $PATH for the interpreter 1305 | """ 1306 | if os.path.abspath(exe) != exe: 1307 | paths = os.environ.get('PATH', '').split(os.pathsep) 1308 | for path in paths: 1309 | if os.path.exists(os.path.join(path, exe)): 1310 | exe = os.path.join(path, exe) 1311 | break 1312 | if not os.path.exists(exe): 1313 | logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) 1314 | raise SystemExit(3) 1315 | if not is_executable(exe): 1316 | logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) 1317 | raise SystemExit(3) 1318 | return exe 1319 | 1320 | def is_executable(exe): 1321 | """Checks a file is executable""" 1322 | return os.access(exe, os.X_OK) 1323 | 1324 | ############################################################ 1325 | ## Relocating the environment: 1326 | 1327 | def make_environment_relocatable(home_dir): 1328 | """ 1329 | Makes the already-existing environment use relative paths, and takes out 1330 | the #!-based environment selection in scripts. 1331 | """ 1332 | home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) 1333 | activate_this = os.path.join(bin_dir, 'activate_this.py') 1334 | if not os.path.exists(activate_this): 1335 | logger.fatal( 1336 | 'The environment doesn\'t have a file %s -- please re-run virtualenv ' 1337 | 'on this environment to update it' % activate_this) 1338 | fixup_scripts(home_dir) 1339 | fixup_pth_and_egg_link(home_dir) 1340 | ## FIXME: need to fix up distutils.cfg 1341 | 1342 | OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], 1343 | 'activate', 'activate.bat', 'activate_this.py'] 1344 | 1345 | def fixup_scripts(home_dir): 1346 | # This is what we expect at the top of scripts: 1347 | shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) 1348 | # This is what we'll put: 1349 | new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] 1350 | activate = "import os; activate_this=os.path.join(os.path.dirname(__file__), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" 1351 | if sys.platform == 'win32': 1352 | bin_suffix = 'Scripts' 1353 | else: 1354 | bin_suffix = 'bin' 1355 | bin_dir = os.path.join(home_dir, bin_suffix) 1356 | home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) 1357 | for filename in os.listdir(bin_dir): 1358 | filename = os.path.join(bin_dir, filename) 1359 | if not os.path.isfile(filename): 1360 | # ignore subdirs, e.g. .svn ones. 1361 | continue 1362 | f = open(filename, 'rb') 1363 | lines = f.readlines() 1364 | f.close() 1365 | if not lines: 1366 | logger.warn('Script %s is an empty file' % filename) 1367 | continue 1368 | if not lines[0].strip().startswith(shebang): 1369 | if os.path.basename(filename) in OK_ABS_SCRIPTS: 1370 | logger.debug('Cannot make script %s relative' % filename) 1371 | elif lines[0].strip() == new_shebang: 1372 | logger.info('Script %s has already been made relative' % filename) 1373 | else: 1374 | logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' 1375 | % (filename, shebang)) 1376 | continue 1377 | logger.notify('Making script %s relative' % filename) 1378 | lines = [new_shebang+'\n', activate+'\n'] + lines[1:] 1379 | f = open(filename, 'wb') 1380 | f.writelines(lines) 1381 | f.close() 1382 | 1383 | def fixup_pth_and_egg_link(home_dir, sys_path=None): 1384 | """Makes .pth and .egg-link files use relative paths""" 1385 | home_dir = os.path.normcase(os.path.abspath(home_dir)) 1386 | if sys_path is None: 1387 | sys_path = sys.path 1388 | for path in sys_path: 1389 | if not path: 1390 | path = '.' 1391 | if not os.path.isdir(path): 1392 | continue 1393 | path = os.path.normcase(os.path.abspath(path)) 1394 | if not path.startswith(home_dir): 1395 | logger.debug('Skipping system (non-environment) directory %s' % path) 1396 | continue 1397 | for filename in os.listdir(path): 1398 | filename = os.path.join(path, filename) 1399 | if filename.endswith('.pth'): 1400 | if not os.access(filename, os.W_OK): 1401 | logger.warn('Cannot write .pth file %s, skipping' % filename) 1402 | else: 1403 | fixup_pth_file(filename) 1404 | if filename.endswith('.egg-link'): 1405 | if not os.access(filename, os.W_OK): 1406 | logger.warn('Cannot write .egg-link file %s, skipping' % filename) 1407 | else: 1408 | fixup_egg_link(filename) 1409 | 1410 | def fixup_pth_file(filename): 1411 | lines = [] 1412 | prev_lines = [] 1413 | f = open(filename) 1414 | prev_lines = f.readlines() 1415 | f.close() 1416 | for line in prev_lines: 1417 | line = line.strip() 1418 | if (not line or line.startswith('#') or line.startswith('import ') 1419 | or os.path.abspath(line) != line): 1420 | lines.append(line) 1421 | else: 1422 | new_value = make_relative_path(filename, line) 1423 | if line != new_value: 1424 | logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) 1425 | lines.append(new_value) 1426 | if lines == prev_lines: 1427 | logger.info('No changes to .pth file %s' % filename) 1428 | return 1429 | logger.notify('Making paths in .pth file %s relative' % filename) 1430 | f = open(filename, 'w') 1431 | f.write('\n'.join(lines) + '\n') 1432 | f.close() 1433 | 1434 | def fixup_egg_link(filename): 1435 | f = open(filename) 1436 | link = f.read().strip() 1437 | f.close() 1438 | if os.path.abspath(link) != link: 1439 | logger.debug('Link in %s already relative' % filename) 1440 | return 1441 | new_link = make_relative_path(filename, link) 1442 | logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) 1443 | f = open(filename, 'w') 1444 | f.write(new_link) 1445 | f.close() 1446 | 1447 | def make_relative_path(source, dest, dest_is_directory=True): 1448 | """ 1449 | Make a filename relative, where the filename is dest, and it is 1450 | being referred to from the filename source. 1451 | 1452 | >>> make_relative_path('/usr/share/something/a-file.pth', 1453 | ... '/usr/share/another-place/src/Directory') 1454 | '../another-place/src/Directory' 1455 | >>> make_relative_path('/usr/share/something/a-file.pth', 1456 | ... '/home/user/src/Directory') 1457 | '../../../home/user/src/Directory' 1458 | >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') 1459 | './' 1460 | """ 1461 | source = os.path.dirname(source) 1462 | if not dest_is_directory: 1463 | dest_filename = os.path.basename(dest) 1464 | dest = os.path.dirname(dest) 1465 | dest = os.path.normpath(os.path.abspath(dest)) 1466 | source = os.path.normpath(os.path.abspath(source)) 1467 | dest_parts = dest.strip(os.path.sep).split(os.path.sep) 1468 | source_parts = source.strip(os.path.sep).split(os.path.sep) 1469 | while dest_parts and source_parts and dest_parts[0] == source_parts[0]: 1470 | dest_parts.pop(0) 1471 | source_parts.pop(0) 1472 | full_parts = ['..']*len(source_parts) + dest_parts 1473 | if not dest_is_directory: 1474 | full_parts.append(dest_filename) 1475 | if not full_parts: 1476 | # Special case for the current directory (otherwise it'd be '') 1477 | return './' 1478 | return os.path.sep.join(full_parts) 1479 | 1480 | 1481 | 1482 | ############################################################ 1483 | ## Bootstrap script creation: 1484 | 1485 | def create_bootstrap_script(extra_text, python_version=''): 1486 | """ 1487 | Creates a bootstrap script, which is like this script but with 1488 | extend_parser, adjust_options, and after_install hooks. 1489 | 1490 | This returns a string that (written to disk of course) can be used 1491 | as a bootstrap script with your own customizations. The script 1492 | will be the standard virtualenv.py script, with your extra text 1493 | added (your extra text should be Python code). 1494 | 1495 | If you include these functions, they will be called: 1496 | 1497 | ``extend_parser(optparse_parser)``: 1498 | You can add or remove options from the parser here. 1499 | 1500 | ``adjust_options(options, args)``: 1501 | You can change options here, or change the args (if you accept 1502 | different kinds of arguments, be sure you modify ``args`` so it is 1503 | only ``[DEST_DIR]``). 1504 | 1505 | ``after_install(options, home_dir)``: 1506 | 1507 | After everything is installed, this function is called. This 1508 | is probably the function you are most likely to use. An 1509 | example would be:: 1510 | 1511 | def after_install(options, home_dir): 1512 | subprocess.call([join(home_dir, 'bin', 'easy_install'), 1513 | 'MyPackage']) 1514 | subprocess.call([join(home_dir, 'bin', 'my-package-script'), 1515 | 'setup', home_dir]) 1516 | 1517 | This example immediately installs a package, and runs a setup 1518 | script from that package. 1519 | 1520 | If you provide something like ``python_version='2.4'`` then the 1521 | script will start with ``#!/usr/bin/env python2.4`` instead of 1522 | ``#!/usr/bin/env python``. You can use this when the script must 1523 | be run with a particular Python version. 1524 | """ 1525 | filename = __file__ 1526 | if filename.endswith('.pyc'): 1527 | filename = filename[:-1] 1528 | f = open(filename, 'rb') 1529 | content = f.read() 1530 | f.close() 1531 | py_exe = 'python%s' % python_version 1532 | content = (('#!/usr/bin/env %s\n' % py_exe) 1533 | + '## WARNING: This file is generated\n' 1534 | + content) 1535 | return content.replace('##EXT' 'END##', extra_text) 1536 | 1537 | ##EXTEND## 1538 | 1539 | def convert(s): 1540 | b = base64.b64decode(s.encode('ascii')) 1541 | return zlib.decompress(b).decode('utf-8') 1542 | 1543 | ##file site.py 1544 | SITE_PY = convert(""" 1545 | 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 1680 | """) 1681 | 1682 | ##file ez_setup.py 1683 | EZ_SETUP_PY = convert(""" 1684 | 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 1750 | BDaonX65d/fwEjNqlDjLVIvM9X+XVxF7 1751 | """) 1752 | 1753 | ##file distribute_setup.py 1754 | DISTRIBUTE_SETUP_PY = convert(""" 1755 | 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 1834 | """) 1835 | 1836 | ##file activate.sh 1837 | ACTIVATE_SH = convert(""" 1838 | eJytVU1v4jAQPW9+xTT0ANVS1GsrDlRFAqmFqmG72m0rY5IJsRRslDiktNr/vuMQ8tFQpNU2B4I9 1839 | H36eeW/SglkgYvBFiLBKYg0LhCRGD1KhA7BjlUQuwkLIHne12HCNNpz5kVrBgsfBmdWCrUrA5VIq 1840 | DVEiQWjwRISuDreW5eE+CtodeLeAnhZEGKMGFXqAciMiJVcoNWx4JPgixDjzEj48QVeCfcqmtzfs 1841 | cfww+zG4ZfeD2ciGF7gCHaDMPM1jtvuHXAsPfF2rSGeOxV4iDY5GUGb3xVEYv2aj6WQ0vRseAlMY 1842 | G5DKsAawwnQUXt2LQOYlzZoYByqhonqoqfxZf4BLD97i4DukgXADCPgGgdOLTK5arYxZB1xnrc9T 1843 | EQFcHoZEAa1gSQioo/TPV5FZrDlxJA+NzwF+Ek1UonOzFnKZp6k5mgLBqSkuuAGXS4whJb5xz/xs 1844 | wXCHjiVerAk5eh9Kfz1wqOldtVv9dkbscfjgjKeTA8XPrtaNauX5rInOxaHuOReNtpFjo1/OxdFG 1845 | 5eY9hJ3L3jqcPJbATggXAemDLZX0MNZRYjSDH7C1wMHQh73DyYfTu8a0F9v+6D8W6XNnF1GEIXW/ 1846 | JrSKPOtnW1YFat9mrLJkzLbyIlTvYzV0RGXcaTBfVLx7jF2PJ2wyuBsydpm7VSVa4C4Zb6pFO2TR 1847 | huypCEPwuQjNftUrNl6GsYZzuFrrLdC9iJjQ3omAPBbcI2lsU77tUD43kw1NPZhTrnZWzuQKLomx 1848 | Rd4OXM1ByExVVkmoTwfBJ7Lt10Iq1Kgo23Bmd8Ib1KrGbsbO4Pp2yO4fpnf3s6MnZiwuiJuls1/L 1849 | Pu4yUCvhpA+vZaJvWWDTr0yFYYyVnHMqCEq+QniuYX225xmnzRENjbXACF3wkCYNVZ1mBwxoR9Iw 1850 | WAo3/36oSOTfgjwEEQKt15e9Xpqm52+oaXxszmnE9GLl65RH2OMmS6+u5acKxDmlPgj2eT5/gQOX 1851 | LLK0j1y0Uwbmn438VZkVpqlfNKa/YET/53j+99G8H8tUhr9ZSXs2 1852 | """) 1853 | 1854 | ##file activate.fish 1855 | ACTIVATE_FISH = convert(""" 1856 | eJydVm1v4jgQ/s6vmA1wBxUE7X2stJVYlVWR2lK13d6d9laRk0yIr8HmbIe0++tvnIQQB9pbXT5A 1857 | Ys/LM55nZtyHx5RrSHiGsMm1gRAh1xhDwU0Kng8hFzMWGb5jBv2E69SDs0TJDdj3MxilxmzPZzP7 1858 | pVPMMl+q9bjXh1eZQ8SEkAZULoAbiLnCyGSvvV6SC7IoBcS4Nw0wjcFbvJDcjiuTswzFDpiIQaHJ 1859 | lQAjQUi1YRmUboC2uZJig8J4PaCnT5IaDcgsbm/CjinOwgx1KcUTMEhhTgV4g2B1fRk8Le8fv86v 1860 | g7v545UHpZB9rKnp+gXsMhxLunIIpwVQxP/l9c/Hq9Xt1epm4R27bva6AJqN92G4YhbMG2i+LB+u 1861 | grv71c3dY7B6WtzfLy9bePbp0taDTXSwJQJszUnnp0y57mvpPcrF7ZODyhswtd59+/jdgw+fwBNS 1862 | xLSscksUPIDqwwNmCez3PpxGeyBYg6HE0YdcWBxcKczYzuVJi5Wu915vn5oWePCCoPUZBN5B7IgV 1863 | MCi54ZDLG7TUZ0HweXkb3M5vFmSpFm/gthhBx0UrveoPpv9AJ9unIbQYdUoe21bKg2q48sPFGVwu 1864 | H+afrxd1qvclaNlRFyh1EQ2sSccEuNAGWQwysfVpz1tPajUqbqJUnEcIJkWo6OXDaodK8ZiLdbmM 1865 | L1wb+9H0D+pcyPSrX5u5kgWSygRYXCnJUi/KKcuU4cqsAyTKZBiissLc7NFwizvjxtieKBVCIdWz 1866 | fzilzPaYyljZN0cGN1v7NnaIPNCGmVy3GKuJaQ6iVjE1Qfm+36hglErwmnAD8hu0dDy4uICBA8ZV 1867 | pQr/q/+O0KFW2kjelu9Dgb9SDBsWV4F4x5CswgS0zBVlk5tDMP5bVtUGpslbm81Lu2sdKq7uNMGh 1868 | MVQ4fy9xhogC1lS5guhISa0DlBWv0O8odT6/LP+4WZzDV6FzIkEqC0uolGZSZoMnlpxplmD2euaT 1869 | O4hkTpPnbztDccey0bhjDaBIqaWQa0uwEtQEwtyU56i4fq54F9IE3ORR6mKriODM4XOYZwaVYLYz 1870 | 7SPbKkz4i7VkB6/Ot1upDE3znNqYKpM8raa0Bx8vfvntJ32UENsM4aI6gJL+jJwhxhh3jVIDOcpi 1871 | m0r2hmEtS8XXXNBk71QCDXTBNhhPiHX2LtHkrVIlhoEshH/EZgdq53Eirqs5iFKMnkOmqZTtr3Xq 1872 | djvPTWZT4S3NT5aVLgurMPUWI07BRVYqkQrmtCKohNY8qu9EdACoT6ki0a66XxVF4f9AQ3W38yO5 1873 | mWmZmIIpnDFrbXakvKWeZhLwhvrbUH8fahhqD0YUcBDJjEBMQwiznE4y5QbHrbhHBOnUAYzb2tVN 1874 | jJa65e+eE2Ya30E2GurxUP8ssA6e/wOnvo3V78d3vTcvMB3n7l3iX1JXWqk= 1875 | """) 1876 | 1877 | ##file activate.csh 1878 | ACTIVATE_CSH = convert(""" 1879 | eJx9U11vmzAUffevOCVRu+UB9pws29Kl0iq1aVWllaZlcgxciiViItsQdb9+xiQp+dh4QOB7Pu49 1880 | XHqY59IgkwVhVRmLmFAZSrGRNkdgykonhFiqSCRW1sJSmJg8wCDT5QrucRCyHn6WFRKhVGmhKwVp 1881 | kUpNiS3emup3TY6XIn7DVNQyJUwlrgthJD6n/iCNv72uhCzCpFx9CRkThRQGKe08cWXJ9db/yh/u 1882 | pvzl9mn+PLnjj5P5D1yM8QmXlzBkSdXwZ0H/BBc0mEo5FE5qI2jKhclHOOvy9HD/OO/6YO1mX9vx 1883 | sY0H/tPIV0dtqel0V7iZvWyNg8XFcBA0ToEqVeqOdNUEQFvN41SumAv32VtJrakQNSmLWmgp4oJM 1884 | yDoBHgoydtoEAs47r5wHHnUal5vbJ8oOI+9wI86vb2d8Nrm/4Xy4RZ8R85E4uTZPB5EZPnTaaAGu 1885 | E59J8BE2J8XgrkbLeXMlVoQxznEYFYY8uFFdxsKQRx90Giwx9vSueHP1YNaUSFG4vTaErNSYuBOF 1886 | lXiVyXa9Sy3JdClEyK1dD6Nos9mEf8iKlOpmqSNTZnYjNEWiUYn2pKNB3ttcLJ3HmYYXy6Un76f7 1887 | r8rRsC1TpTJj7f19m5sUf/V3Ir+x/yjtLu8KjLX/CmN/AcVGUUo= 1888 | """) 1889 | 1890 | ##file activate.bat 1891 | ACTIVATE_BAT = convert(""" 1892 | eJyFUkEKgzAQvAfyhz0YaL9QEWpRqlSjWGspFPZQTevFHOr/adQaU1GaUzI7Mzu7ZF89XhKkEJS8 1893 | qxaKMMsvboQ+LxxE44VICSW1gEa2UFaibqoS0iyJ0xw2lIA6nX5AHCu1jpRsv5KRjknkac9VLVug 1894 | sX9mtzxIeJDE/mg4OGp47qoLo3NHX2jsMB3AiDht5hryAUOEifoTdCXbSh7V0My2NMq/Xbh5MEjU 1895 | ZT63gpgNT9lKOJ/CtHsvT99re3pX303kydn4HeyOeAg5cjf2EW1D6HOPkg9NGKhu 1896 | """) 1897 | 1898 | ##file deactivate.bat 1899 | DEACTIVATE_BAT = convert(""" 1900 | eJxzSE3OyFfIT0vj4spMU0hJTcvMS01RiPf3cYkP8wwKCXX0iQ8I8vcNCFHQ4FIAguLUEgWIgK0q 1901 | FlWqXJpcICVYpGzx2BAZ4uHv5+Hv6wq1BWINXBTdKriEKkI1DhW2QAfhttcxxANiFZCBbglQSJUL 1902 | i2dASrm4rFz9XLgAwJNbyQ== 1903 | """) 1904 | 1905 | ##file distutils-init.py 1906 | DISTUTILS_INIT = convert(""" 1907 | eJytV92L4zYQf/dfMU0ottuse7RvC6FQrg8Lxz2Ugz4si9HacqKuIxlJ2ST313dG8odkO9d7aGBB 1908 | luZLv/nNjFacOqUtKJMIvzK3cXlhWgp5MDBsqK5SNYftsBAGpLLA4F1oe2Ytl+9wUvW55TswCi4c 1909 | KibhbFDSglXQCFmDPXIwtm7FawLRbwtPzg2T9gf4gupKv4GS0N262w7V0NvpbCy8cvTo3eAus6C5 1910 | ETU3ICQZX1hFTw/dzR6V/AW1RCN4/XAtbsVXqIXmlVX6liS4lOzEYY9QFB2zx6LfoSNjz1a0pqT9 1911 | QOIfJWQ2E888NEVZNqLlZZnvIB0NpHkimlFdKn2iRRY7yGG/CCJb6Iz280d34SFXBS2yEYPNF0Q7 1912 | yM7oCjpWvbEDQmnhRwOs6zjThpKE8HogwRAgraqYFZgGZvzmzVh+mgz9vskT3hruwyjdFcqyENJw 1913 | bbMPO5jdzonxK68QKT7B57CMRRG5shRSWDTX3dI8LzRndZbnSWL1zfvriUmK4TcGWSnZiEPCrxXv 1914 | bM+sP7VW2is2WgWXCO3sAu3Rzysz3FiNCA8WPyM4gb1JAAmCiyTZbhFjWx3h9SzauuRXC9MFoVbc 1915 | yNTCm1QXOOIfIn/g1kGMhDUBN72hI5XCBQtIXQw8UEEdma6Jaz4vJIJ51Orc15hzzmu6TdFp3ogr 1916 | Aof0c98tsw1SiaiWotHffk3XYCkqdToxWRfTFXqgpg2khcLluOHMVC0zZhLKIomesfSreUNNgbXi 1917 | Ky9VRzwzkBneNoGQyyvGjbsFQqOZvpWIjqH281lJ/jireFgR3cPzSyTGWzQpDNIU+03Fs4XKLkhp 1918 | /n0uFnuF6VphB44b3uWRneSbBoMSioqE8oeF0JY+qTvYfEK+bPLYdoR4McfYQ7wMZj39q0kfP8q+ 1919 | FfsymO0GzNlPh644Jje06ulqHpOEQqdJUfoidI2O4CWx4qOglLye6RrFQirpCRXvhoRqXH3sYdVJ 1920 | AItvc+VUsLO2v2hVAWrNIfVGtkG351cUMNncbh/WdowtSPtCdkzYFv6mwYc9o2Jt68ud6wectBr8 1921 | hYAulPSlgzH44YbV3ikjrulEaNJxt+/H3wZ7bXSXje/YY4tfVVrVmUstaDwwOBLMg6iduDB0lMVC 1922 | UyzYx7Ab4kjCqdViEJmDcdk/SKbgsjYXgfMznUWcrtS4z4fmJ/XOM1LPk/iIpqass5XwNbdnLb1Y 1923 | 8h3ERXSWZI6rZJxKs1LBqVH65w0Oy4ra0CBYxEeuOMbDmV5GI6E0Ha/wgVTtkX0+OXvqsD02CKLf 1924 | XHbeft85D7tTCMYy2Njp4DJP7gWJr6paVWXZ1+/6YXLv/iE0M90FktiI7yFJD9e7SOLhEkkaMTUO 1925 | azq9i2woBNR0/0eoF1HFMf0H8ChxH/jgcB34GZIz3Qn4/vid+VEamQrOVqAPTrOfmD4MPdVh09tb 1926 | 8dLLjvh/61lEP4yW5vJaH4vHcevG8agXvzPGoOhhXNncpTr99PTHx6e/UvffFLaxUSjuSeP286Dw 1927 | gtEMcW1xKr/he4/6IQ6FUXP+0gkioHY5iwC9Eyx3HKO7af0zPPe+XyLn7fAY78k4aiR387bCr5XT 1928 | 5C4rFgwLGfMvJuAMew== 1929 | """) 1930 | 1931 | ##file distutils.cfg 1932 | DISTUTILS_CFG = convert(""" 1933 | eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH 1934 | xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg 1935 | 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= 1936 | """) 1937 | 1938 | ##file activate_this.py 1939 | ACTIVATE_THIS = convert(""" 1940 | eJyNUlGL2zAMfvevEBlHEujSsXsL9GGDvW1jD3sZpQQ3Ua7aJXawnbT595Ocpe0dO5ghseVP+vRJ 1941 | VpIkn2cYPZknwAvWLXWYhRP5Sk4baKgOWRWNqtpdgTyH2Y5wpq5Tug406YAgKEzkwqg7NBPwR86a 1942 | Hk0olPopaK0NHJHzYQPnE5rI0o8+yBUwiBfyQcT8mMPJGiAT0A0O+b8BY4MKJ7zPcSSzHaKrSpJE 1943 | qeDmUgGvVbPCS41DgO+6xy/OWbfAThMn/OQ9ukDWRCSLiKzk1yrLjWapq6NnvHUoHXQ4bYPdrsVX 1944 | 4lQMc/q6ZW975nmSK+oH6wL42a9H65U6aha342Mh0UVDzrD87C1bH73s16R5zsStkBZDp0NrXQ+7 1945 | HaRnMo8f06UBnljKoOtn/YT+LtdvSyaT/BtIv9KR60nF9f3qmuYKO4//T9ItJMsjPfgUHqKwCZ3n 1946 | xu/Lx8M/UvCLTxW7VULHxB1PRRbrYfvWNY5S8it008jOjcleaMqVBDnUXcWULV2YK9JEQ92OfC96 1947 | 1Tv4ZicZZZ7GpuEpZbbeQ7DxquVx5hdqoyFSSmXwfC90f1Dc7hjFs/tK99I0fpkI8zSLy4tSy+sI 1948 | 3vMWehjQNJmE5VePlZbL61nzX3S93ZcfDqznnkb9AZ3GWJU= 1949 | """) 1950 | 1951 | if __name__ == '__main__': 1952 | main() 1953 | 1954 | ## TODO: 1955 | ## Copy python.exe.manifest 1956 | ## Monkeypatch distutils.sysconfig 1957 | --------------------------------------------------------------------------------