├── setup.py ├── migrations └── 0001.sql ├── records_migrate ├── __init__.py ├── models.py ├── cli.py └── adapters.py ├── t.py ├── Pipfile ├── README.md ├── .gitignore ├── LICENSE └── Pipfile.lock /setup.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/0001.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /records_migrate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t.py: -------------------------------------------------------------------------------- 1 | from records import Database 2 | from records_migrate.adapters import MigrationAdapter 3 | 4 | db = Database() 5 | a = MigrationAdapter(db=db) 6 | print(a.last_migration_applied) 7 | # a.init() 8 | # for file in a.load(): 9 | # print(file) 10 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | records = "*" 8 | flake8 = "*" 9 | black = "*" 10 | pytest = "*" 11 | psycopg2 = "*" 12 | 13 | [packages] 14 | docopt = "*" 15 | click = "*" 16 | crayons = "*" 17 | 18 | [requires] 19 | python_version = "3.7" 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /records_migrate/models.py: -------------------------------------------------------------------------------- 1 | class Migration: 2 | def __init__(self, *, path, adapter): 3 | self.path = path 4 | self.adapter = adapter 5 | 6 | def __repr__(self): 7 | return f"" 8 | 9 | @property 10 | def text(self): 11 | """Returns the text of the query.""" 12 | with open(self.path, 'r') as f: 13 | return f.read() 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Records-Migrate: a migration system for Records 2 | 3 | A migration system for the [Records](https://github.com/kennethreitz/records) Python library. 4 | 5 | ## Intented Usage 6 | 7 | Assuming `DATABASE_URL` is set: 8 | 9 | $ records-migrate check 10 | all migrations appear to be applied! 11 | 12 | $ records-migrate new 13 | Created file migrations/0003.sql. Feel free to add a suffix to the file name. 14 | 15 | $ records-migrate apply 16 | Applied migration 3/5...,, 17 | 18 | $ records-migrate schema 19 | Dumps out SQL script for generating the schema. 20 | 21 | ## Considerations 22 | 23 | - Don't base migration order on file creation time, as Heroku strips the data on deploy. 24 | -------------------------------------------------------------------------------- /records_migrate/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Naval Fate. 3 | 4 | Usage: 5 | records-migrate check 6 | records-migrate new 7 | records-migrate apply 8 | records-migrate schema 9 | 10 | Options: 11 | -h --help Show this screen. 12 | --version Show version. 13 | --db= Database to connect to [default: $DATABASE_URL]. 14 | """ 15 | 16 | import os 17 | import sys 18 | 19 | # import crayons 20 | from docopt import docopt 21 | 22 | def puts_error(msg, exit_code=False): 23 | if exit_code is True: 24 | exit_code = 1 25 | 26 | # msg = f"{str(crayons.red('Error'))}: {msg}" 27 | msg = f"Error: {msg}" 28 | print(msg, file=sys.stderr) 29 | 30 | if isinstance(exit_code, int): 31 | sys.exit(exit_code) 32 | 33 | 34 | def main(): 35 | args = docopt(doc=__doc__) 36 | 37 | do_check = args['check'] 38 | do_new = args['new'] 39 | do_apply = args['apply'] 40 | do_schema = args['schema'] 41 | 42 | # Prepare database_url. 43 | if "--db" not in args: 44 | try: 45 | args["--db"] = os.environ["DATABASE_URL"] 46 | except KeyError: 47 | puts_error("Either --db or $DATABASE_URL must be provided!", exit_code=1) 48 | 49 | 50 | if do_check: 51 | pass 52 | 53 | elif do_new: 54 | pass 55 | 56 | elif do_apply: 57 | pass 58 | 59 | elif do_schema: 60 | pass 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .vscode 106 | -------------------------------------------------------------------------------- /records_migrate/adapters.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .models import Migration 4 | 5 | 6 | class MigrationAdapter: 7 | def __init__(self, db, *, _dir="migrations", default_format="{:06d}"): 8 | # Make path of dir absolute. 9 | _dir = os.path.abspath(_dir) 10 | 11 | # Create the migration directory, if it doesn't exist 12 | os.makedirs(_dir, exist_ok=True) 13 | 14 | # The migrations directory. 15 | self.migrations_dir = _dir 16 | 17 | # The database. 18 | self.db = db 19 | 20 | @property 21 | def files(self): 22 | """Returns a list of file names in the migrations directory, in order.""" 23 | 24 | def gen(): 25 | for root, dirs, files in os.walk( 26 | self.migrations_dir, topdown=True, followlinks=True 27 | ): 28 | for _file in files: 29 | # Yield the file name. 30 | yield f"{root}{os.path.sep}{_file}" 31 | 32 | return [g for g in gen()] 33 | 34 | def load(self): 35 | def gen(): 36 | for _file in self.files: 37 | yield Migration(path=_file, adapter=self) 38 | 39 | return [g for g in gen()] 40 | 41 | def query(self, query, **kwargs): 42 | return self.db.query(query, **kwargs) 43 | 44 | def init(self): 45 | q = """ 46 | CREATE TABLE migrations ( 47 | name varchar(80), 48 | timestamp timestamp 49 | ); 50 | 51 | ALTER TABLE migrations ALTER COLUMN timestamp SET DEFAULT now(); 52 | """ 53 | 54 | self.query(q) 55 | 56 | @property 57 | def last_migration_applied(self): 58 | records = self.query("SELECT * from migrations;") 59 | try: 60 | result = records[0]["name"] 61 | except IndexError: 62 | result = None 63 | 64 | return result 65 | 66 | def mark_success(self, migration_id): 67 | self.query( 68 | "INSERT INTO migrations (name) VALUES (:migration_id);", 69 | migration_id=migration_id, 70 | ) 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "eff8afd597adb8a7d54fa0e772abf913770b977144a5a47bee0f57700d15eb90" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "click": { 20 | "hashes": [ 21 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 22 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 23 | ], 24 | "index": "pypi", 25 | "version": "==7.0" 26 | }, 27 | "colorama": { 28 | "hashes": [ 29 | "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", 30 | "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" 31 | ], 32 | "version": "==0.4.1" 33 | }, 34 | "crayons": { 35 | "hashes": [ 36 | "sha256:5e17691605e564d63482067eb6327d01a584bbaf870beffd4456a3391bd8809d", 37 | "sha256:6f51241d0c4faec1c04c1c0ac6a68f1d66a4655476ce1570b3f37e5166a599cc" 38 | ], 39 | "index": "pypi", 40 | "version": "==0.1.2" 41 | }, 42 | "docopt": { 43 | "hashes": [ 44 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" 45 | ], 46 | "index": "pypi", 47 | "version": "==0.6.2" 48 | } 49 | }, 50 | "develop": { 51 | "appdirs": { 52 | "hashes": [ 53 | "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", 54 | "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" 55 | ], 56 | "version": "==1.4.3" 57 | }, 58 | "atomicwrites": { 59 | "hashes": [ 60 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", 61 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" 62 | ], 63 | "version": "==1.3.0" 64 | }, 65 | "attrs": { 66 | "hashes": [ 67 | "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", 68 | "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" 69 | ], 70 | "version": "==18.2.0" 71 | }, 72 | "black": { 73 | "hashes": [ 74 | "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", 75 | "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5" 76 | ], 77 | "index": "pypi", 78 | "version": "==18.9b0" 79 | }, 80 | "click": { 81 | "hashes": [ 82 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 83 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 84 | ], 85 | "index": "pypi", 86 | "version": "==7.0" 87 | }, 88 | "colorama": { 89 | "hashes": [ 90 | "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", 91 | "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" 92 | ], 93 | "version": "==0.4.1" 94 | }, 95 | "defusedxml": { 96 | "hashes": [ 97 | "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", 98 | "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" 99 | ], 100 | "version": "==0.5.0" 101 | }, 102 | "docopt": { 103 | "hashes": [ 104 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" 105 | ], 106 | "index": "pypi", 107 | "version": "==0.6.2" 108 | }, 109 | "entrypoints": { 110 | "hashes": [ 111 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 112 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 113 | ], 114 | "version": "==0.3" 115 | }, 116 | "et-xmlfile": { 117 | "hashes": [ 118 | "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b" 119 | ], 120 | "version": "==1.0.1" 121 | }, 122 | "flake8": { 123 | "hashes": [ 124 | "sha256:09b9bb539920776da542e67a570a5df96ff933c9a08b62cfae920bcc789e4383", 125 | "sha256:e0f8cd519cfc0072c0ee31add5def09d2b3ef6040b34dc426445c3af9b02163c" 126 | ], 127 | "index": "pypi", 128 | "version": "==3.7.4" 129 | }, 130 | "jdcal": { 131 | "hashes": [ 132 | "sha256:948fb8d079e63b4be7a69dd5f0cd618a0a57e80753de8248fd786a8a20658a07", 133 | "sha256:ea0a5067c5f0f50ad4c7bdc80abad3d976604f6fb026b0b3a17a9d84bb9046c9" 134 | ], 135 | "version": "==1.4" 136 | }, 137 | "mccabe": { 138 | "hashes": [ 139 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 140 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 141 | ], 142 | "version": "==0.6.1" 143 | }, 144 | "more-itertools": { 145 | "hashes": [ 146 | "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", 147 | "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", 148 | "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" 149 | ], 150 | "version": "==5.0.0" 151 | }, 152 | "odfpy": { 153 | "hashes": [ 154 | "sha256:596021f0519623ca8717331951c95e3b8d7b21e86edc7efe8cb650a0d0f59a2b" 155 | ], 156 | "version": "==1.4.0" 157 | }, 158 | "openpyxl": { 159 | "hashes": [ 160 | "sha256:93c00466fe15469bffc74e05ce83982b6efb5e0c1529cea13dc2024379db5d3b" 161 | ], 162 | "version": "==2.6.0b1" 163 | }, 164 | "pluggy": { 165 | "hashes": [ 166 | "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", 167 | "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" 168 | ], 169 | "version": "==0.8.1" 170 | }, 171 | "psycopg2": { 172 | "hashes": [ 173 | "sha256:02445ebbb3a11a3fe8202c413d5e6faf38bb75b4e336203ee144ca2c46529f94", 174 | "sha256:0e9873e60f98f0c52339abf8f0339d1e22bfe5aae0bcf7aabd40c055175035ec", 175 | "sha256:1148a5eb29073280bf9057c7fc45468592c1bb75a28f6df1591adb93c8cb63d0", 176 | "sha256:259a8324e109d4922b0fcd046e223e289830e2568d6f4132a3702439e5fd532b", 177 | "sha256:28dffa9ed4595429e61bacac41d3f9671bb613d1442ff43bcbec63d4f73ed5e8", 178 | "sha256:314a74302d4737a3865d40ea50e430ce1543c921ba10f39d562e807cfe2edf2a", 179 | "sha256:36b60201b6d215d7658a71493fdf6bd5e60ad9a0cffed39906627ff9f4f3afd3", 180 | "sha256:3f9d532bce54c4234161176ff3b8688ff337575ca441ea27597e112dfcd0ee0c", 181 | "sha256:5d222983847b40af989ad96c07fc3f07e47925e463baa5de716be8f805b41d9b", 182 | "sha256:6757a6d2fc58f7d8f5d471ad180a0bd7b4dd3c7d681f051504fbea7ae29c8d6f", 183 | "sha256:6a0e0f1e74edb0ab57d89680e59e7bfefad2bfbdf7c80eb38304d897d43674bb", 184 | "sha256:6ca703ccdf734e886a1cf53eb702261110f6a8b0ed74bcad15f1399f74d3f189", 185 | "sha256:8513b953d8f443c446aa79a4cc8a898bd415fc5e29349054f03a7d696d495542", 186 | "sha256:9262a5ce2038570cb81b4d6413720484cb1bc52c064b2f36228d735b1f98b794", 187 | "sha256:97441f851d862a0c844d981cbee7ee62566c322ebb3d68f86d66aa99d483985b", 188 | "sha256:a07feade155eb8e69b54dd6774cf6acf2d936660c61d8123b8b6b1f9247b67d6", 189 | "sha256:a9b9c02c91b1e3ec1f1886b2d0a90a0ea07cc529cb7e6e472b556bc20ce658f3", 190 | "sha256:ae88216f94728d691b945983140bf40d51a1ff6c7fe57def93949bf9339ed54a", 191 | "sha256:b360ffd17659491f1a6ad7c928350e229c7b7bd83a2b922b6ee541245c7a776f", 192 | "sha256:b4221957ceccf14b2abdabef42d806e791350be10e21b260d7c9ce49012cc19e", 193 | "sha256:b90758e49d5e6b152a460d10b92f8a6ccf318fcc0ee814dcf53f3a6fc5328789", 194 | "sha256:c669ea986190ed05fb289d0c100cc88064351f2b85177cbfd3564c4f4847d18c", 195 | "sha256:d1b61999d15c79cf7f4f7cc9021477aef35277fc52452cf50fd13b713c84424d", 196 | "sha256:de7bb043d1adaaf46e38d47e7a5f703bb3dab01376111e522b07d25e1a79c1e1", 197 | "sha256:e393568e288d884b94d263f2669215197840d097c7e5b0acd1a51c1ea7d1aba8", 198 | "sha256:ed7e0849337bd37d89f2c2b0216a0de863399ee5d363d31b1e5330a99044737b", 199 | "sha256:f153f71c3164665d269a5d03c7fa76ba675c7a8de9dc09a4e2c2cdc9936a7b41", 200 | "sha256:f1fb5a8427af099beb7f65093cbdb52e021b8e6dbdfaf020402a623f4181baf5", 201 | "sha256:f36b333e9f86a2fba960c72b90c34be6ca71819e300f7b1fc3d2b0f0b2c546cd", 202 | "sha256:f4526d078aedd5187d0508aa5f9a01eae6a48a470ed678406da94b4cd6524b7e" 203 | ], 204 | "index": "pypi", 205 | "version": "==2.7.7" 206 | }, 207 | "py": { 208 | "hashes": [ 209 | "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", 210 | "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" 211 | ], 212 | "version": "==1.7.0" 213 | }, 214 | "pycodestyle": { 215 | "hashes": [ 216 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 217 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 218 | ], 219 | "version": "==2.5.0" 220 | }, 221 | "pyflakes": { 222 | "hashes": [ 223 | "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", 224 | "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" 225 | ], 226 | "version": "==2.1.0" 227 | }, 228 | "pytest": { 229 | "hashes": [ 230 | "sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07", 231 | "sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d" 232 | ], 233 | "index": "pypi", 234 | "version": "==4.2.0" 235 | }, 236 | "pyyaml": { 237 | "hashes": [ 238 | "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", 239 | "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", 240 | "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", 241 | "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", 242 | "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b" 243 | ], 244 | "version": "==4.2b4" 245 | }, 246 | "records": { 247 | "hashes": [ 248 | "sha256:238cba35e8efbb724493bbb195bd027d9e78db4a978597969a7af0f722ac3686", 249 | "sha256:6d060a2b44ecc198d4e86efd5dab8558a2581b4019970bd8839e1604a243f57e" 250 | ], 251 | "index": "pypi", 252 | "version": "==0.5.2" 253 | }, 254 | "six": { 255 | "hashes": [ 256 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 257 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 258 | ], 259 | "version": "==1.12.0" 260 | }, 261 | "sqlalchemy": { 262 | "hashes": [ 263 | "sha256:c08cee353acaa05dd4ddf8ae0b0844ae779ed88e0b0784a2c9e0c0f9118eb64c" 264 | ], 265 | "version": "==1.3.0b2" 266 | }, 267 | "tablib": { 268 | "hashes": [ 269 | "sha256:b8cf50a61d66655229993f2ee29220553fb2c80403479f8e6de77c0c24649d87" 270 | ], 271 | "version": "==0.12.1" 272 | }, 273 | "toml": { 274 | "hashes": [ 275 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 276 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 277 | ], 278 | "version": "==0.10.0" 279 | }, 280 | "unicodecsv": { 281 | "hashes": [ 282 | "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc" 283 | ], 284 | "version": "==0.14.1" 285 | }, 286 | "xlrd": { 287 | "hashes": [ 288 | "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2", 289 | "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde" 290 | ], 291 | "version": "==1.2.0" 292 | }, 293 | "xlwt": { 294 | "hashes": [ 295 | "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", 296 | "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" 297 | ], 298 | "version": "==1.3.0" 299 | } 300 | } 301 | } 302 | --------------------------------------------------------------------------------