diff --git a/.travis.yml b/.travis.yml index c8bdbe9..a5d05a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,10 @@ language: python +dist: xenial python: - - "2.6" - "2.7" + - "3.5" + - "3.6" + - "3.7" # command to install dependencies install: "pip install -r requirements-dev.txt" # command to run tests diff --git a/CHANGELOG b/CHANGELOG index ccf2a42..a45ed2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +* Fri Mar 08 2019 Sundeep Anand - 1.5.3 +- ZNTA-2791 - Port Zanata Python client to Python 3 + * Mon Apr 16 2018 Sundeep Anand - 1.5.2 - ZNTA-1390 - Python client fuzzies my strings - ZNTA-1382 - Should "--push-trans-only" be deprecated in the python client? diff --git a/Makefile b/Makefile index d76143c..45db0f9 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,11 @@ clean: python setup.py clean rm -f zanataclient/VERSION-FILE +clean-pyc: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + run: python zanata help @@ -28,7 +33,7 @@ lint-report: pylint --reports=n zanata zanataclient flake8: - flake8 --ignore=E501,F403,F841,F401 zanataclient + flake8 --ignore=E402,E501,F403,F841,F401 zanataclient test: (cd zanataclient/test; nosetests ${NOSE_FLAGS} test_all.py) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9c998a0..df23342 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,16 +1,7 @@ -r requirements.txt -coverage==4.0.3 -flake8==2.4.1 -flake8-import-order==0.6.1 -funcsigs==0.4 -linecache2==1.0.0 -mccabe==0.3.1 -MiniMock==1.2.8 -mock==1.3.0 -nose==1.3.7 -pbr==1.8.1 -pep8==1.5.7 -pyflakes==0.8.1 -six==1.10.0 -traceback2==1.4.0 -unittest2==1.1.0 +coverage +flake8<3.8 +flake8-import-order +mock +nose +unittest2 diff --git a/requirements.txt b/requirements.txt index 1312935..21a0af6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ +future httplib2 lxml -ordereddict polib diff --git a/setup.py b/setup.py index fec649b..97970d1 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def get_client_version(): path = os.path.dirname(os.path.realpath(__file__)) version_file = os.path.join(path, 'zanataclient', 'VERSION-FILE') try: - version = open(version_file, 'rb') + version = open(version_file, 'r') client_version = version.read() version.close() version_number = client_version.rstrip().strip('version: ') @@ -65,7 +65,9 @@ def get_client_version(): 'License :: OSI Approved :: GNU Lesser General Public License (LGPL)', 'Operating System :: Unix', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], ) diff --git a/zanataclient/VERSION-FILE b/zanataclient/VERSION-FILE index 2905a79..0babd5f 100644 --- a/zanataclient/VERSION-FILE +++ b/zanataclient/VERSION-FILE @@ -1 +1 @@ -version: 1.5.2 +version: 1.5.3 diff --git a/zanataclient/cmdbase.py b/zanataclient/cmdbase.py index a4c3629..64ddac7 100644 --- a/zanataclient/cmdbase.py +++ b/zanataclient/cmdbase.py @@ -428,7 +428,7 @@ def check_plural_support(self, server_version): version = str(server_version.split('-')[0]) main_ver = version[:3] - version_number = string.atof(main_ver) + version_number = float(main_ver) if version_number >= 1.6: return True diff --git a/zanataclient/command.py b/zanataclient/command.py index f54ea9c..3cde349 100644 --- a/zanataclient/command.py +++ b/zanataclient/command.py @@ -323,7 +323,7 @@ def handle_program( path = os.path.dirname(os.path.realpath(__file__)) version_file = os.path.join(path, 'VERSION-FILE') try: - version = open(version_file, 'rb') + version = open(version_file, 'r') client_version = version.read() version.close() version_number = client_version.rstrip().strip('version: ') diff --git a/zanataclient/context.py b/zanataclient/context.py index 4556e93..952c4cf 100644 --- a/zanataclient/context.py +++ b/zanataclient/context.py @@ -164,7 +164,7 @@ def _update_client_version(self): version_file = os.path.join(path, client_version_file) try: - version = open(version_file, 'rb') + version = open(version_file, 'r') client_version = version.read() version.close() version_number = client_version.rstrip()[len('version: '):] @@ -224,7 +224,7 @@ def _update_locale_mapping(self): locale_map = self.process_locales(locales) self.remote_config.update({'locale_map': locale_map}) - def _update_project_type(self): + def update_project_type(self): """ This fetches project_type from server """ @@ -271,7 +271,7 @@ def get_remote_configs(self): """ context_remote_configs = { 'default': [self._update_server_version, self._update_locale_mapping, - self._update_project_type], + self.update_project_type], 'init': [], } return context_remote_configs[self.mode] @@ -287,14 +287,18 @@ def get_context_data(self): """ updates context_data with remote_config, local_config and command_dict """ + + def merge_dicts(i, j): + z = i.copy() + z.update(j) + return z + build_configs = [self.build_local_config, self.build_remote_config] [method() for method in build_configs] - # lowest to higest + # lowest to highest precedence = [self.remote_config, self.local_config, self.command_dict] - context_data = functools.reduce( - lambda option, value: dict(option.items() + value.items()), precedence - ) + context_data = functools.reduce(lambda option, value: merge_dicts(option, value), precedence) return self.filter_context_data(context_data) def filter_context_data(self, data): diff --git a/zanataclient/csvconverter.py b/zanataclient/csvconverter.py index dcf441e..64ddcac 100644 --- a/zanataclient/csvconverter.py +++ b/zanataclient/csvconverter.py @@ -111,6 +111,7 @@ def convert_to_json(self, filepath, locale_map, comments_header): # glossary = {'source-locales':srclocales, 'glossary-entries':entries, 'target-locales':targetlocales} return json.dumps(glossary) + if __name__ == "__main__": converter = CSVConverter() converter.convert_to_json("~/Downloads/test_data.csv", {'es': 'es-ES'}, ["description", "pos"]) diff --git a/zanataclient/initcmd.py b/zanataclient/initcmd.py index 93fba51..ab103c7 100644 --- a/zanataclient/initcmd.py +++ b/zanataclient/initcmd.py @@ -23,27 +23,21 @@ import fnmatch import os import sys -from datetime import datetime - -from .cmdbase import CommandsBase, CommandsInit -from .context import ContextBase -from .parseconfig import ZanataConfig -from .zanatalib.logger import Logger, TextColour -from .zanatalib.projectutils import ToolBox - - try: - from collections import OrderedDict + from builtins import input except ImportError: - from ordereddict import OrderedDict + from __builtin__ import input +from collections import OrderedDict +from datetime import datetime +from zanataclient.cmdbase import CommandsBase, CommandsInit +from zanataclient.context import ContextBase +from zanataclient.parseconfig import ZanataConfig +from zanataclient.zanatalib.logger import Logger, TextColour +from zanataclient.zanatalib.projectutils import ToolBox -log = Logger() -try: - input = raw_input -except NameError: - pass +log = Logger() class ZanataInit(CommandsInit, ContextBase): @@ -69,7 +63,7 @@ def get_init_stages(self): self._update_user_config, self._update_http_headers, self._update_client_version, self.check_config_exists, self.set_zanata_command, self.project_options, - self.version_options, self._update_project_type, + self.version_options, self.update_project_type, self.post_project_version, self.source_target_dirs, self.dump_project_config, self.whats_next] } @@ -170,8 +164,8 @@ def _create_new_project(self): log.info("Creating project on the server...") if not self.zanata_cmd.create_project(input_project_id, input_project_name, input_project_desc, project_type): - raise - except: + raise Exception + except Exception as e: if self.print_yes_no("[?] Do you want to try again (y/n)? "): return self._create_new_project() else: @@ -214,8 +208,8 @@ def _create_new_version(self): log.info("Creating version on the server...") if not self.zanata_cmd.create_version(self.local_config.get('project_id'), input_version_id): - raise - except: + raise Exception + except Exception as e: if self.print_yes_no("[?] Do you want to try again (y/n)? "): return self._create_new_version() else: @@ -335,7 +329,7 @@ def dump_project_config(self): xmldoc = ToolBox.dict2xml('config', project_config_dict) try: with open('zanata.xml', 'w') as project_config_file: - project_config_file.write(xmldoc) + project_config_file.write(xmldoc.decode("utf-8")) except IOError: log.error("Something went wrong. Try Again.") else: diff --git a/zanataclient/parseconfig.py b/zanataclient/parseconfig.py index 98d3ce0..f6d4cfc 100644 --- a/zanataclient/parseconfig.py +++ b/zanataclient/parseconfig.py @@ -24,6 +24,7 @@ ) import os.path +from collections import OrderedDict from xml.dom import minidom from .zanatalib.logger import Logger @@ -34,10 +35,6 @@ except ImportError: from configparser import ConfigParser -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict project_config = {} @@ -52,7 +49,7 @@ def set_userconfig(self, path): try: self.configparser = ConfigParser() self._config = self.configparser.read(['zanata.ini', path]) - except: + except Exception: self.log.error("Invalid User Config") def get_server(self, url): diff --git a/zanataclient/publicanutil.py b/zanataclient/publicanutil.py index 7066001..5a4ee93 100644 --- a/zanataclient/publicanutil.py +++ b/zanataclient/publicanutil.py @@ -184,7 +184,7 @@ def create_txtflowtarget(self, pofile): def validate_content_type(self, content_type, object_type): PATTERN = r'.+? charset=([\w_\-:\.]+)' - rxt = re.compile(unicode(PATTERN)) + rxt = re.compile(PATTERN) match = rxt.search(content_type) if match: diff --git a/zanataclient/test/test_all.py b/zanataclient/test/test_all.py index 1c5f8e9..76d4ba2 100644 --- a/zanataclient/test/test_all.py +++ b/zanataclient/test/test_all.py @@ -21,18 +21,13 @@ import unittest -from test_client import RestHandleTest - -from test_context import ProjectContextTest - -from test_parseconfig import ConfigTest - -from test_publicanutil import PublicanUtilityTest - -from test_service import ServiceTest +from zanataclient.test.test_client import RestHandleTest +from zanataclient.test.test_context import ProjectContextTest +from zanataclient.test.test_parseconfig import ConfigTest +from zanataclient.test.test_publicanutil import PublicanUtilityTest +from zanataclient.test.test_service import ServiceTest # from test_zanata import ZanataTest - # from test_zanatacmd import ZanataCmdTest suite = unittest.TestSuite() diff --git a/zanataclient/test/test_client.py b/zanataclient/test/test_client.py index 940fbd1..e3d8cda 100644 --- a/zanataclient/test/test_client.py +++ b/zanataclient/test/test_client.py @@ -58,6 +58,7 @@ def previous(self): def status(self): return 200 + response = FalseDict() response.update({'status': '200', 'access-control-allow-headers': 'X-Requested-With, Content-Type, Accept,', 'content-location': 'http://localhost:8080/zanata/seam/resource/restv1/projects', @@ -67,9 +68,9 @@ def status(self): 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET', 'content-type': 'application/json'}) -content = u'[{"id":"black-silver","defaultType":"","name":"Black Silver",' \ - u'"links":[{"href":"p/black-silver","rel":"self","type":"application/vnd.zanata.project+json"}],' \ - u'"status":"ACTIVE"}]' +content = b'[{"id":"black-silver","defaultType":"","name":"Black Silver",' \ + b'"links":[{"href":"p/black-silver","rel":"self","type":"application/vnd.zanata.project+json"}],' \ + b'"status":"ACTIVE"}]' class RestHandleTest(unittest.TestCase): @@ -98,5 +99,6 @@ def test_get_response_content(self, mock_call_request): self.assertTrue('links' in response_content[1], 'links should be in content') self.assertTrue('status' in response_content[1], 'project status should be in content') + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/test/test_context.py b/zanataclient/test/test_context.py index fd360a9..e6d00ae 100644 --- a/zanataclient/test/test_context.py +++ b/zanataclient/test/test_context.py @@ -172,5 +172,6 @@ def test_init_context(self): self.assertTrue('servers' in context_data) self.assertTrue('http://localhost:8080/zanata' in context_data['servers']) + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/test/test_parseconfig.py b/zanataclient/test/test_parseconfig.py index 83acabd..3204062 100644 --- a/zanataclient/test/test_parseconfig.py +++ b/zanataclient/test/test_parseconfig.py @@ -60,5 +60,6 @@ def test_project_config(self): self.assertEqual(project_config['file_mapping_rules']['**/po/*.pot'], '{path}/{locale_with_underscore}.po') + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/test/test_publicanutil.py b/zanataclient/test/test_publicanutil.py index f06af36..872ce8d 100644 --- a/zanataclient/test/test_publicanutil.py +++ b/zanataclient/test/test_publicanutil.py @@ -81,5 +81,7 @@ def test_msgstrplural(self): self.assertEqual(result['extensions'], expect_json['extensions']) json_data.close() """ + + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/test/test_service.py b/zanataclient/test/test_service.py index 71c7745..5a61d97 100644 --- a/zanataclient/test/test_service.py +++ b/zanataclient/test/test_service.py @@ -83,5 +83,6 @@ def test_messages_status_401_503(self): self.service.messages(SERVICE_RESPONSE_503, RESPONSE_CONTENT) self.assertEqual(ex.exception.code, 1) + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/test/test_zanatacmd.py b/zanataclient/test/test_zanatacmd.py index 0fef3ae..3878dad 100644 --- a/zanataclient/test/test_zanatacmd.py +++ b/zanataclient/test/test_zanatacmd.py @@ -70,5 +70,6 @@ def test_pull_command(self): def test_push_command(self): pass + if __name__ == '__main__': unittest.main() diff --git a/zanataclient/zanatacmd.py b/zanataclient/zanatacmd.py index d42b666..235bdfe 100644 --- a/zanataclient/zanatacmd.py +++ b/zanataclient/zanatacmd.py @@ -165,7 +165,7 @@ def del_server_content(self, tmlfolder, project_id, iteration_id, push_files, fo for name in filelist: delete = False - request = name.replace(',', '\,').replace('/', ',') + request = name.replace(",", "\\,").replace("/", ",") if ".pot" in name: path = os.path.join(tmlfolder, name) @@ -205,13 +205,13 @@ def list_projects(self): sys.exit(1) for project in projects: - print("\nProject ID: %s") % project.id - print("Project Name: %s") % project.name + print("\nProject ID: {0}".format(project.id)) + print("Project Name: {0}".format(project.name)) if hasattr(project, 'defaultType') and project.defaultType.strip(): - print("Project Type: %s") % project.defaultType - print("Project Links: %s") % [{'href': link.href, 'type': link.type, 'rel': link.rel} for link in project.links] + print("Project Type: {0}".format(project.defaultType)) + print("Project Links: {0}".format([{'href': link.href, 'type': link.type, 'rel': link.rel} for link in project.links])) if hasattr(project, 'status'): - print("Project Status: %s") % project.status + print("Project Status: {0}".format(project.status)) def project_info(self, project_id): """ @@ -219,17 +219,17 @@ def project_info(self, project_id): """ try: p = self.zanata_resource.projects.get(project_id) - print("\nProject ID: %s") % p.id - print("Project Name: %s") % p.name + print("\nProject ID: {0}".format(p.id)) + print("Project Name: {0}".format(p.name)) if hasattr(p, 'defaultType') and p.defaultType.strip(): - print("Project Type: %s") % p.defaultType + print("Project Type: {0}".format(p.defaultType)) if hasattr(p, 'description') and p.description.strip(): - print("Project Desc: %s") % p.description + print("Project Desc: {0}".format(p.description)) if hasattr(p, 'status'): - print("Project Status: %s") % p.status + print("Project Status: {0}".format(p.status)) versions = self.get_project_versions(project_id) if versions: - print(" %s Version(s): [%s]") % (len(versions), ", ".join(versions)) + print("{0} Version(s): [ {1} ]".format(len(versions), ", ".join(versions))) print("\n") except NoSuchProjectException as e: self.log.error(str(e)) @@ -256,11 +256,15 @@ def version_info(self, project_id, iteration_id): try: project = self.zanata_resource.projects.get(project_id) iteration = project.get_iteration(iteration_id) - print("\nVersion ID: %s") % iteration.id + print("\nVersion ID: {0}".format(iteration.id)) if hasattr(iteration, 'name'): - print("Version Name: %s") % iteration.name + print("Version Name: {0}".format(iteration.name)) if hasattr(iteration, 'description'): - print("Version Description: %s") % iteration.description + print("Version Description: {0}".format(iteration.description)) + if hasattr(iteration, 'projectType') and iteration.projectType.strip(): + print("Version Type: {0}".format(iteration.projectType)) + if hasattr(iteration, 'status'): + print("Version Status: {0}".format(iteration.status)) # This can be implemented with some flag etc. # filelist = self.zanata_resource.documents.get_file_list(project_id, iteration_id) # if filelist: diff --git a/zanataclient/zanatalib/logger.py b/zanataclient/zanatalib/logger.py index 057d5c1..b88e5ab 100644 --- a/zanataclient/zanatalib/logger.py +++ b/zanataclient/zanatalib/logger.py @@ -48,28 +48,24 @@ def __init__(self): def success(self, message): if self.enable_successprefix: - print(TextColour.OKGREEN + self.success_prefix - + message + TextColour.ENDC) + print(TextColour.OKGREEN + self.success_prefix + message + TextColour.ENDC) else: print(message) def info(self, message): if self.enable_infoprefix: - print(TextColour.OKBLUE + self.info_prefix - + message + TextColour.ENDC) + print(TextColour.OKBLUE + self.info_prefix + message + TextColour.ENDC) else: print(message) def warn(self, message): if self.enable_warnprefix: - print(TextColour.WARNING + self.warn_prefix - + message + TextColour.ENDC) + print(TextColour.WARNING + self.warn_prefix + message + TextColour.ENDC) else: print(message) def error(self, message): if self.enable_errprefix: - print(TextColour.FAIL + self.error_prefix - + message + TextColour.ENDC) + print(TextColour.FAIL + self.error_prefix + message + TextColour.ENDC) else: print(message) diff --git a/zanataclient/zanatalib/projectservice.py b/zanataclient/zanatalib/projectservice.py index dad63b5..6665574 100644 --- a/zanataclient/zanatalib/projectservice.py +++ b/zanataclient/zanatalib/projectservice.py @@ -89,7 +89,7 @@ def create(self, project): res, content = self.restclient.process_request( 'create_project', project.id, body=self._to_unicode(body), headers=self.http_headers ) - return self.messages(res, content, "The project is already exist on server") + return self.messages(res, content, "The project {0} could not be created.".format(project.id)) def delete(self): pass diff --git a/zanataclient/zanatalib/projectutils.py b/zanataclient/zanatalib/projectutils.py index ba1359f..b2a74e4 100644 --- a/zanataclient/zanatalib/projectutils.py +++ b/zanataclient/zanatalib/projectutils.py @@ -71,8 +71,7 @@ def _get_doc_trans_percent(self, doc_name, stats_dict): for stat in stats_dict: if stat.get('locale'): trans_percent.update({ - stat['locale']: int((float(stat.get('translated', 0) * 100) // - float(stat.get('total', 0)))) + stat['locale']: int((float(stat.get('translated', 0) * 100) // float(stat.get('total', 0)))) }) return {doc_name: trans_percent} @@ -148,7 +147,7 @@ def populate_etree(element, data): element.text = v else: # set attribute - element.set(k, unicode(v)) + element.set(k, str(v)) @staticmethod def dict2xml(root_elem, dict_object): diff --git a/zanataclient/zanatalib/rest/config.py b/zanataclient/zanatalib/rest/config.py index 225ee41..b91c02c 100644 --- a/zanataclient/zanatalib/rest/config.py +++ b/zanataclient/zanatalib/rest/config.py @@ -19,20 +19,15 @@ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +from collections import OrderedDict from collections import namedtuple -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict - - middle_url = '/seam/resource/restv1' http_methods = ('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS') media_types = ('application/json', 'application/vnd.zanata.projects+json', 'application/vnd.zanata.Version+json', 'application/vnd.zanata.project.iteration+json', 'application/vnd.zanata.glossary+json', - 'application/vnd.zanata.project.locales+json', 'application/xml') + 'application/vnd.zanata.project.locales+json', 'application/xml', 'application/json;charset=ISO-8859-1') # based on https://zanata.ci.cloudbees.com/job/zanata-api-site/site/zanata-common-api/rest-api-docs/index.html # please add, modify resource details here, and make entry in service-to-resource mappings and in zpc_services diff --git a/zanataclient/zanatalib/service.py b/zanataclient/zanatalib/service.py index dae5656..7adf037 100644 --- a/zanataclient/zanatalib/service.py +++ b/zanataclient/zanatalib/service.py @@ -2,7 +2,7 @@ import json import sys -from error import ( +from zanataclient.zanatalib.error import ( BadRequestBodyException, ForbiddenException, InternalServerError, @@ -14,10 +14,9 @@ UnavailableServiceError, UnexpectedStatusException, ) -from projectutils import ToolBox - -from rest.client import RestClient -from rest.config import media_types +from zanataclient.zanatalib.projectutils import ToolBox +from zanataclient.zanatalib.rest.client import RestClient +from zanataclient.zanatalib.rest.config import media_types __all__ = ( @@ -44,7 +43,7 @@ class Service(object): def __init__(self, *args, **kargs): for name, val in zip(self._fields, args): setattr(self, name, val) - for key, value in kargs.iteritems(): + for key, value in kargs.items(): setattr(self, key, value) self.restclient = RestClient(self.base_url) @@ -52,7 +51,7 @@ def excption_handler(self, exception_class, error, error_msg): try: raise exception_class(error, error_msg) except exception_class as e: - print '', e + print(str(e)) finally: sys.exit(1) @@ -64,7 +63,7 @@ def messages(self, res, content, extra_msg=None): try: rst = ToolBox.xmlstring2dict(content) \ if res.get('content-type') and 'xml' in res['content-type'] else json.loads(content) - except ValueError, e: + except ValueError as e: if content.strip() == "": return rst if res.get('content-type') and res['content-type'] not in media_types: @@ -74,6 +73,8 @@ def messages(self, res, content, extra_msg=None): sys.exit(1) else: print("Exception while decoding: %s" % e) + if isinstance(content, str): + print(content) return rst elif res['status'] == '201': return True @@ -84,6 +85,6 @@ def messages(self, res, content, extra_msg=None): 'Error', 'Unexpected Status (%s), failed to push: %s' % (res['status'], extra_msg or "")) def _to_unicode(self, some_string): - if not isinstance(some_string, unicode): - return unicode(some_string) + if not isinstance(some_string, str): + return str(some_string) return some_string