diff --git a/.pylintrc b/.pylintrc index 49f40fdd570..6f669cb758a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -50,7 +50,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,attribute-defined-outside-init +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,attribute-defined-outside-init,bad-continuation # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/.stickler.yml b/.stickler.yml index 7479d99bd96..c093071d08d 100644 --- a/.stickler.yml +++ b/.stickler.yml @@ -1,7 +1,8 @@ linters: flake8: enable: true - ignore: F401,E226,I101,I100,E741 + ignore: E203, E266, E501, W503, F401, E741 + max-line-length: 88 shellcheck: shell: bash csslint: diff --git a/.travis.yml b/.travis.yml index 48dab3c1743..9087dc5387d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ env: - secure: "md4fgPt9RC/sCoN5//5PcNHLUd9gWQGewV5hFpWW88MRTjxTng1Zfs8r7SqlF2AkEEepFfyzq0BEe9c3FMAnFbec3KmqdlQen4V8xDbLrcTlvkPlTrYGbAScUvdhhqojB//hMHoTD4KvxAv9CiUwFBO4hCMmj2buWHUbV9Ksu5WCW9mF/gkt/hIuYAU6Mbwt8PiYyMgUpzMHO1vruofcWRaVnvKwmBqHB0ae86D4/drpwn4CWjlM12WUnphT2bssiyPkw24FZtCN6kPVta6bLZKBxu0bZpw2vbXuUG+Yh19Q4mp8wNYT3XSHJf8Hl5LfujF48+cLWu+6rlCkdcelyVylhWLFc3rGOONAv4G8jWW2yNSz/bLQfJnMpd81fQEu5eySmFxB7mdB0uyKpvIG1jMJQ73LlYKakKLAPdYhMFyQAHoX9gvCE3S4QR95DBMi5gM/pZubOCcMLdjPHB5JKpJHSjxbOzyVwgmsUIEgd5Bi2vZvvYQXn1plk4xpQ3PhXc+/gi33bzY89mKcfOn0HJ2pD1vLqDXRCBsMCakoLZ0JB/6bacaz4FngbsGWuQ+I1cz20lJGL/MSi9bW1G7Uoidt3GXXWDmXrWt70vIXlLIxr8XV0Mu/rPbauGgWE+ZSYEfvdM5sP+FNF7vQ5de+Fkvzg5Z3tTfR+O1W+d7+vM4=" - TWINE_USERNAME=Leonardo.Uieda - COVERAGE=false + - CHECK=false - BUILD_DOCS=false - DEPLOY_DOCS=false - DEPLOY_PYPI=false @@ -35,6 +36,7 @@ matrix: env: - PYTHON=3.6 - COVERAGE=true + - CHECK=true - BUILD_DOCS=true - DEPLOY_DOCS=true - DEPLOY_PYPI=true @@ -69,9 +71,10 @@ install: - pip install dist/* script: - # Check code for PEP8 style and lint for code quality - - make pep8 - - make lint + # Check code for style and lint for code quality + - if [ "$CHECK" == "true" ]; then + make check; + fi # Run the test suite - if [ "$COVERAGE" == "true" ]; then make coverage; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de64c106d0b..2e2fd97c163 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,7 @@ # Contributing Guidelines -First off, thank you for considering contributing to GMT/Python! -It is a community-driven project, so it's people like you that make it useful -and successful. +First off, thank you for considering contributing to GMT/Python! It is a +community-driven project, so it's people like you that make it useful and successful. We love contributions from community members, just like you! There are many ways to contribute: @@ -12,10 +11,9 @@ There are many ways to contribute: * Improvements to the documentation * Writing code which can be incorporated into project for everyone to use -If you get stuck at -any point you can create an -[issue on GitHub](https://github.com/GenericMappingTools/gmt-python/issues) -or contact us at one of the other channels mentioned below. +If you get stuck at any point you can create an [issue on +GitHub](https://github.com/GenericMappingTools/gmt-python/issues) or contact us at one +of the other channels mentioned below. For more information on contributing to open source projects, [GitHub's own guide](https://guides.github.com/activities/contributing-to-open-source/) @@ -124,6 +122,9 @@ Activate it by running: source activate gmt-python + +## Automation + The `Makefile` provides rules for installing, running the tests and coverage analysis, running linters, etc. If you don't want to use `make`, see the [Makefile](Makefile) and copy the @@ -143,8 +144,16 @@ and the coverage analysis using: make coverage -To check your code for PEP8 style and common errors (runs `flake8` and -`pylint`): +We use [Black](https://github.com/ambv/black) to format the code so we don't have to +think about it. You don't have to worry about formatting the code yourself. Before +committing, run the following to automatically format your code: + + make format + +Don't worry if you forget to do it. Our continuous integration systems will warn us and +you can make a new commit with the formatted code. + +To check your code for style and common errors (runs `black` and `pylint`): make check @@ -183,8 +192,7 @@ quickly: for documentation. Pull requests will automatically have tests run by TravisCI. -This includes running both the unit tests as well as the `flake8` and `pylint` -code linters. +This includes running both the unit tests as well as `black` and `pylint`. Github will show the status of these checks on the pull request. Try to get them all passing (green). If you have any trouble, leave a comment asking for help. diff --git a/Makefile b/Makefile index 4ab44600720..4d7bb63773e 100644 --- a/Makefile +++ b/Makefile @@ -3,15 +3,15 @@ TESTDIR=tmp-test-dir-with-unique-name PYTEST_ARGS=--doctest-modules -v --pyargs PYTEST_COV_ARGS=--cov-config=../.coveragerc --cov-report=term-missing +CHECK_FILES=gmt setup.py help: @echo "Commands:" @echo "" @echo " develop install in editable mode" @echo " test run the test suite (including doctests)" - @echo " check run all code quality checks (pep8, linter)" - @echo " pep8 check for PEP8 style compliance" - @echo " lint run static analysis using pylint" + @echo " check run code quality checks (black and pylint)" + @echo " format run black to automatically format the code" @echo " coverage calculate test coverage" @echo " clean clean up build and generated files" @echo "" @@ -34,13 +34,12 @@ coverage: cp $(TESTDIR)/.coverage* . rm -r $(TESTDIR) -pep8: - flake8 gmt setup.py +format: + black $(CHECK_FILES) -lint: - pylint gmt setup.py - -check: pep8 lint +check: + black --check $(CHECK_FILES) + pylint $(CHECK_FILES) clean: find . -name "*.pyc" -exec rm -v {} \; diff --git a/environment.yml b/environment.yml index b2183ddf918..b586f174a6a 100644 --- a/environment.yml +++ b/environment.yml @@ -18,7 +18,7 @@ dependencies: - pytest-cov - pytest-mpl - coverage - - flake8 + - black - pylint - sphinx - sphinx_rtd_theme diff --git a/gmt/__init__.py b/gmt/__init__.py index c8e69ac9b28..d63fd167b0a 100644 --- a/gmt/__init__.py +++ b/gmt/__init__.py @@ -22,8 +22,8 @@ # Get the version number through versioneer -__version__ = _get_versions()['version'] -__commit__ = _get_versions()['full-revisionid'] +__version__ = _get_versions()["version"] +__commit__ = _get_versions()["full-revisionid"] # Start our global modern mode session _begin() @@ -43,15 +43,15 @@ def print_libgmt_info(): columns = shutil.get_terminal_size().columns title = "Currently loaded libgmt" - left = (columns - len(title) - 2)//2 - right = left + (columns - (2*left + len(title) + 2)) - header = ' '.join(['='*left, title, '='*right]) + left = (columns - len(title) - 2) // 2 + right = left + (columns - (2 * left + len(title) + 2)) + header = " ".join(["=" * left, title, "=" * right]) with LibGMT() as lib: lines = [header] for key in sorted(lib.info): - lines.append('{}: {}'.format(key, lib.info[key])) - print('\n'.join(lines)) + lines.append("{}: {}".format(key, lib.info[key])) + print("\n".join(lines)) def test(doctest=True, verbose=True, coverage=False, figures=True): @@ -91,15 +91,15 @@ def test(doctest=True, verbose=True, coverage=False, figures=True): args = [] if verbose: - args.append('-vv') + args.append("-vv") if coverage: - args.append('--cov=gmt') - args.append('--cov-report=term-missing') + args.append("--cov=gmt") + args.append("--cov-report=term-missing") if doctest: - args.append('--doctest-modules') + args.append("--doctest-modules") if figures: - args.append('--mpl') - args.append('--pyargs') - args.append('gmt') + args.append("--mpl") + args.append("--pyargs") + args.append("gmt") status = pytest.main(args) assert status == 0, "Some tests have failed." diff --git a/gmt/_version.py b/gmt/_version.py index 242f763f57e..a3ff13304c9 100644 --- a/gmt/_version.py +++ b/gmt/_version.py @@ -58,17 +58,18 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None @@ -76,10 +77,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + p = subprocess.Popen( + [c] + args, + cwd=cwd, + env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None), + ) break except EnvironmentError: e = sys.exc_info()[1] @@ -116,16 +120,22 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + "version": dirname[len(parentdir_prefix) :], + "full-revisionid": None, + "dirty": False, + "error": None, + "date": None, + } else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print( + "Tried directories %s but none started with prefix %s" + % (str(rootdirs), parentdir_prefix) + ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -181,7 +191,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -190,7 +200,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = set([r for r in refs if re.search(r"\d", r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -198,19 +208,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None, + "date": date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags", + "date": None, + } @register_vcs_handler("git", "pieces_from_vcs") @@ -225,8 +242,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -234,10 +250,19 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = run_command( + GITS, + [ + "describe", + "--tags", + "--dirty", + "--always", + "--long", + "--match", + "%s*" % tag_prefix, + ], + cwd=root, + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -260,17 +285,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -279,10 +303,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( + full_tag, + tag_prefix, + ) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -293,13 +319,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[ + 0 + ].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -330,8 +356,7 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -445,11 +470,13 @@ def render_git_describe_long(pieces): def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None, + } if not style or style == "default": style = "pep440" # the default @@ -469,9 +496,13 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None, + "date": pieces.get("date"), + } def get_versions(): @@ -485,8 +516,7 @@ def get_versions(): verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -495,13 +525,16 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for i in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None, + } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -515,6 +548,10 @@ def get_versions(): except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", + "date": None, + } diff --git a/gmt/base_plotting.py b/gmt/base_plotting.py index 95466c8ce63..36611978a70 100644 --- a/gmt/base_plotting.py +++ b/gmt/base_plotting.py @@ -4,11 +4,17 @@ """ from .clib import LibGMT from .exceptions import GMTInvalidInput -from .helpers import build_arg_string, dummy_context, data_kind, \ - fmt_docstring, use_alias, kwargs_to_strings +from .helpers import ( + build_arg_string, + dummy_context, + data_kind, + fmt_docstring, + use_alias, + kwargs_to_strings, +) -class BasePlotting(): +class BasePlotting: """ Base class for Figure and Subplot. @@ -45,10 +51,19 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use return kwargs @fmt_docstring - @use_alias(R='region', J='projection', A='area_thresh', B='frame', - D='resolution', I='rivers', N='borders', W='shorelines', - G='land', S='water') - @kwargs_to_strings(R='sequence') + @use_alias( + R="region", + J="projection", + A="area_thresh", + B="frame", + D="resolution", + I="rivers", + N="borders", + W="shorelines", + G="land", + S="water", + ) + @kwargs_to_strings(R="sequence") def coast(self, **kwargs): """ Plot continents, shorelines, rivers, and borders on maps @@ -108,11 +123,11 @@ def coast(self, **kwargs): """ kwargs = self._preprocess(**kwargs) with LibGMT() as lib: - lib.call_module('coast', build_arg_string(kwargs)) + lib.call_module("coast", build_arg_string(kwargs)) @fmt_docstring - @use_alias(R='region', J='projection', B='frame', I='shading', C='cmap') - @kwargs_to_strings(R='sequence') + @use_alias(R="region", J="projection", B="frame", I="shading", C="cmap") + @kwargs_to_strings(R="sequence") def grdimage(self, grid, **kwargs): """ Project grids or images and plot them on maps. @@ -132,24 +147,31 @@ def grdimage(self, grid, **kwargs): kwargs = self._preprocess(**kwargs) kind = data_kind(grid, None, None) with LibGMT() as lib: - if kind == 'file': + if kind == "file": file_context = dummy_context(grid) - elif kind == 'grid': + elif kind == "grid": raise NotImplementedError( - "Sorry, DataArray support is not yet functional.") + "Sorry, DataArray support is not yet functional." + ) else: - raise GMTInvalidInput("Unrecognized data type: {}" - .format(type(grid))) + raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid))) with file_context as fname: - arg_str = ' '.join([fname, build_arg_string(kwargs)]) - lib.call_module('grdimage', arg_str) + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("grdimage", arg_str) @fmt_docstring - @use_alias(R='region', J='projection', B='frame', S='style', G='color', - W='pen', i='columns', C='cmap') - @kwargs_to_strings(R='sequence', i='sequence_comma') - def plot(self, x=None, y=None, data=None, sizes=None, direction=None, - **kwargs): + @use_alias( + R="region", + J="projection", + B="frame", + S="style", + G="color", + W="pen", + i="columns", + C="cmap", + ) + @kwargs_to_strings(R="sequence", i="sequence_comma") + def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): """ Plot lines, polygons, and symbols on maps. @@ -221,36 +243,38 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, kind = data_kind(data, x, y) extra_arrays = [] - if 'S' in kwargs and kwargs['S'][0] in 'vV' and direction is not None: + if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None: extra_arrays.extend(direction) - if 'G' in kwargs and not isinstance(kwargs['G'], str): - if kind != 'vectors': + if "G" in kwargs and not isinstance(kwargs["G"], str): + if kind != "vectors": raise GMTInvalidInput( - "Can't use arrays for color if data is matrix or file.") - extra_arrays.append(kwargs['G']) - del kwargs['G'] + "Can't use arrays for color if data is matrix or file." + ) + extra_arrays.append(kwargs["G"]) + del kwargs["G"] if sizes is not None: - if kind != 'vectors': + if kind != "vectors": raise GMTInvalidInput( - "Can't use arrays for sizes if data is matrix or file.") + "Can't use arrays for sizes if data is matrix or file." + ) extra_arrays.append(sizes) with LibGMT() as lib: # Choose how data will be passed in to the module - if kind == 'file': + if kind == "file": file_context = dummy_context(data) - elif kind == 'matrix': + elif kind == "matrix": file_context = lib.matrix_to_vfile(data) - elif kind == 'vectors': + elif kind == "vectors": file_context = lib.vectors_to_vfile(x, y, *extra_arrays) with file_context as fname: - arg_str = ' '.join([fname, build_arg_string(kwargs)]) - lib.call_module('plot', arg_str) + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("plot", arg_str) @fmt_docstring - @use_alias(R='region', J='projection', B='frame') - @kwargs_to_strings(R='sequence') + @use_alias(R="region", J="projection", B="frame") + @kwargs_to_strings(R="sequence") def basemap(self, **kwargs): """ Produce a basemap for the figure. @@ -290,18 +314,16 @@ def basemap(self, **kwargs): """ kwargs = self._preprocess(**kwargs) - if not ('B' in kwargs or 'L' in kwargs or 'T' in kwargs): - raise GMTInvalidInput( - "At least one of B, L, or T must be specified.") - if 'D' in kwargs and 'F' not in kwargs: - raise GMTInvalidInput( - "Option D requires F to be specified as well.") + if not ("B" in kwargs or "L" in kwargs or "T" in kwargs): + raise GMTInvalidInput("At least one of B, L, or T must be specified.") + if "D" in kwargs and "F" not in kwargs: + raise GMTInvalidInput("Option D requires F to be specified as well.") with LibGMT() as lib: - lib.call_module('basemap', build_arg_string(kwargs)) + lib.call_module("basemap", build_arg_string(kwargs)) @fmt_docstring - @use_alias(R='region', J='projection') - @kwargs_to_strings(R='sequence') + @use_alias(R="region", J="projection") + @kwargs_to_strings(R="sequence") def logo(self, **kwargs): """ Place the GMT graphics logo on a map. @@ -329,7 +351,7 @@ def logo(self, **kwargs): """ kwargs = self._preprocess(**kwargs) - if 'D' not in kwargs: + if "D" not in kwargs: raise GMTInvalidInput("Option D must be specified.") with LibGMT() as lib: - lib.call_module('logo', build_arg_string(kwargs)) + lib.call_module("logo", build_arg_string(kwargs)) diff --git a/gmt/clib/core.py b/gmt/clib/core.py index ecc600c6f5d..209c8dd8e59 100644 --- a/gmt/clib/core.py +++ b/gmt/clib/core.py @@ -9,13 +9,22 @@ from packaging.version import Version import numpy as np -from ..exceptions import GMTCLibError, GMTCLibNoSessionError, \ - GMTInvalidInput, GMTVersionError -from .utils import load_libgmt, kwargs_to_ctypes_array, vectors_to_arrays, \ - dataarray_to_matrix, as_c_contiguous - - -class LibGMT(): # pylint: disable=too-many-instance-attributes +from ..exceptions import ( + GMTCLibError, + GMTCLibNoSessionError, + GMTInvalidInput, + GMTVersionError, +) +from .utils import ( + load_libgmt, + kwargs_to_ctypes_array, + vectors_to_arrays, + dataarray_to_matrix, + as_c_contiguous, +) + + +class LibGMT: # pylint: disable=too-many-instance-attributes """ Load and access the GMT shared library (libgmt). @@ -59,48 +68,39 @@ class LibGMT(): # pylint: disable=too-many-instance-attributes """ data_families = [ - 'GMT_IS_DATASET', - 'GMT_IS_GRID', - 'GMT_IS_PALETTE', - 'GMT_IS_MATRIX', - 'GMT_IS_VECTOR', + "GMT_IS_DATASET", + "GMT_IS_GRID", + "GMT_IS_PALETTE", + "GMT_IS_MATRIX", + "GMT_IS_VECTOR", ] - data_vias = [ - 'GMT_VIA_MATRIX', - 'GMT_VIA_VECTOR', - ] + data_vias = ["GMT_VIA_MATRIX", "GMT_VIA_VECTOR"] data_geometries = [ - 'GMT_IS_NONE', - 'GMT_IS_POINT', - 'GMT_IS_LINE', - 'GMT_IS_POLYGON', - 'GMT_IS_PLP', - 'GMT_IS_SURFACE', + "GMT_IS_NONE", + "GMT_IS_POINT", + "GMT_IS_LINE", + "GMT_IS_POLYGON", + "GMT_IS_PLP", + "GMT_IS_SURFACE", ] - data_modes = [ - 'GMT_CONTAINER_ONLY', - 'GMT_OUTPUT', - ] + data_modes = ["GMT_CONTAINER_ONLY", "GMT_OUTPUT"] - grid_registrations = [ - 'GMT_GRID_PIXEL_REG', - 'GMT_GRID_NODE_REG', - ] + grid_registrations = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"] # The minimum version of GMT required - required_version = '6.0.0' + required_version = "6.0.0" # Map numpy dtypes to GMT types _dtypes = { - 'float64': 'GMT_DOUBLE', - 'float32': 'GMT_FLOAT', - 'int64': 'GMT_LONG', - 'int32': 'GMT_INT', - 'uint64': 'GMT_ULONG', - 'uint32': 'GMT_UINT', + "float64": "GMT_DOUBLE", + "float32": "GMT_FLOAT", + "int64": "GMT_LONG", + "int32": "GMT_INT", + "uint64": "GMT_ULONG", + "uint32": "GMT_UINT", } @property @@ -115,10 +115,15 @@ def current_session(self): outside of the context manager). """ - if not hasattr(self, '_session_id') or self._session_id is None: - raise GMTCLibNoSessionError(' '.join([ - "No currently open GMT API session.", - "Use only inside a 'with' block."])) + if not hasattr(self, "_session_id") or self._session_id is None: + raise GMTCLibNoSessionError( + " ".join( + [ + "No currently open GMT API session.", + "Use only inside a 'with' block.", + ] + ) + ) return self._session_id @current_session.setter @@ -134,17 +139,17 @@ def info(self): Dictionary with the GMT version and default paths and parameters. """ infodict = { - 'version': self.get_default('API_VERSION'), - 'padding': self.get_default("API_PAD"), - 'binary dir': self.get_default("API_BINDIR"), - 'share dir': self.get_default("API_SHAREDIR"), + "version": self.get_default("API_VERSION"), + "padding": self.get_default("API_PAD"), + "binary dir": self.get_default("API_BINDIR"), + "share dir": self.get_default("API_SHAREDIR"), # This segfaults for some reason # 'data dir': self.get_default("API_DATADIR"), - 'plugin dir': self.get_default("API_PLUGINDIR"), - 'library path': self.get_default("API_LIBRARY"), - 'cores': self.get_default("API_CORES"), - 'image layout': self.get_default("API_IMAGE_LAYOUT"), - 'grid layout': self.get_default("API_GRID_LAYOUT"), + "plugin dir": self.get_default("API_PLUGINDIR"), + "library path": self.get_default("API_LIBRARY"), + "cores": self.get_default("API_CORES"), + "image layout": self.get_default("API_IMAGE_LAYOUT"), + "grid layout": self.get_default("API_GRID_LAYOUT"), } return infodict @@ -181,7 +186,7 @@ def get_libgmt_func(self, name, argtypes=None, restype=None): ._FuncPtr'> """ - if not hasattr(self, '_libgmt'): + if not hasattr(self, "_libgmt"): self._libgmt = load_libgmt() function = getattr(self._libgmt, name) if argtypes is not None: @@ -194,15 +199,17 @@ def __enter__(self): """ Start the GMT session and keep the session argument. """ - self.current_session = self.create_session('gmt-python-session') + self.current_session = self.create_session("gmt-python-session") # Need to store the version info because 'get_default' won't work after # the session is destroyed. - version = self.info['version'] + version = self.info["version"] if Version(version) < Version(self.required_version): self._cleanup_session() raise GMTVersionError( - "Using an incompatible GMT version {}. Must be newer than {}." - .format(version, self.required_version)) + "Using an incompatible GMT version {}. Must be newer than {}.".format( + version, self.required_version + ) + ) return self def __exit__(self, exc_type, exc_value, traceback): @@ -242,19 +249,20 @@ def create_session(self, session_name): """ c_create_session = self.get_libgmt_func( - 'GMT_Create_Session', - argtypes=[ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint, - ctypes.c_void_p], - restype=ctypes.c_void_p) + "GMT_Create_Session", + argtypes=[ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p], + restype=ctypes.c_void_p, + ) # None is passed in place of the print function pointer. It becomes the # NULL pointer when passed to C, prompting the C API to use the default # print function. print_func = None - padding = self.get_constant('GMT_PAD_DEFAULT') - session_type = self.get_constant('GMT_SESSION_EXTERNAL') - session = c_create_session(session_name.encode(), padding, - session_type, print_func) + padding = self.get_constant("GMT_PAD_DEFAULT") + session_type = self.get_constant("GMT_SESSION_EXTERNAL") + session = c_create_session( + session_name.encode(), padding, session_type, print_func + ) if session is None: raise GMTCLibError("Failed to create a GMT API void pointer.") @@ -279,13 +287,12 @@ def destroy_session(self, session): """ c_destroy_session = self.get_libgmt_func( - 'GMT_Destroy_Session', - argtypes=[ctypes.c_void_p], - restype=ctypes.c_int) + "GMT_Destroy_Session", argtypes=[ctypes.c_void_p], restype=ctypes.c_int + ) status = c_destroy_session(session) if status: - raise GMTCLibError('Failed to destroy GMT API session') + raise GMTCLibError("Failed to destroy GMT API session") def get_constant(self, name): """ @@ -312,13 +319,13 @@ def get_constant(self, name): """ c_get_enum = self.get_libgmt_func( - 'GMT_Get_Enum', argtypes=[ctypes.c_char_p], restype=ctypes.c_int) + "GMT_Get_Enum", argtypes=[ctypes.c_char_p], restype=ctypes.c_int + ) value = c_get_enum(name.encode()) if value is None or value == -99999: - raise GMTCLibError( - "Constant '{}' doesn't exits in libgmt.".format(name)) + raise GMTCLibError("Constant '{}' doesn't exits in libgmt.".format(name)) return value @@ -356,9 +363,10 @@ def get_default(self, name): """ c_get_default = self.get_libgmt_func( - 'GMT_Get_Default', + "GMT_Get_Default", argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p], - restype=ctypes.c_int) + restype=ctypes.c_int, + ) # Make a string buffer to get a return value value = ctypes.create_string_buffer(10000) @@ -367,8 +375,10 @@ def get_default(self, name): if status != 0: raise GMTCLibError( - "Error getting default value for '{}' (error code {})." - .format(name, status)) + "Error getting default value for '{}' (error code {}).".format( + name, status + ) + ) return value.value.decode() @@ -409,24 +419,28 @@ def log_to_file(self, logfile=None): """ c_handle_messages = self.get_libgmt_func( - 'GMT_Handle_Messages', - argtypes=[ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, - ctypes.c_char_p], - restype=ctypes.c_int) + "GMT_Handle_Messages", + argtypes=[ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_char_p], + restype=ctypes.c_int, + ) if logfile is None: - tmp_file = NamedTemporaryFile(prefix='gmt-python-', suffix='.log', - delete=False) + tmp_file = NamedTemporaryFile( + prefix="gmt-python-", suffix=".log", delete=False + ) logfile = tmp_file.name tmp_file.close() - status = c_handle_messages(self.current_session, - self.get_constant('GMT_LOG_ONCE'), - self.get_constant('GMT_IS_FILE'), - logfile.encode()) + status = c_handle_messages( + self.current_session, + self.get_constant("GMT_LOG_ONCE"), + self.get_constant("GMT_IS_FILE"), + logfile.encode(), + ) if status != 0: msg = "Failed to set logging to file '{}' (error: {}).".format( - logfile, status) + logfile, status + ) raise GMTCLibError(msg) # The above is called when entering a 'with' statement @@ -459,18 +473,17 @@ def call_module(self, module, args): """ c_call_module = self.get_libgmt_func( - 'GMT_Call_Module', - argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, - ctypes.c_void_p], - restype=ctypes.c_int) + "GMT_Call_Module", + argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p], + restype=ctypes.c_int, + ) - mode = self.get_constant('GMT_MODULE_CMD') + mode = self.get_constant("GMT_MODULE_CMD") # If there is no open session, this will raise an exception. Can' let # it happen inside the 'with' otherwise the logfile won't be deleted. session = self.current_session with self.log_to_file() as logfile: - status = c_call_module(session, module.encode(), mode, - args.encode()) + status = c_call_module(session, module.encode(), mode, args.encode()) # Get the error message inside the with block before the log file # is deleted with open(logfile) as flog: @@ -478,15 +491,17 @@ def call_module(self, module, args): # Raise the exception outside the log 'with' to make sure the logfile # is cleaned. if status != 0: - if log == '': + if log == "": msg = "Invalid GMT module name '{}'.".format(module) else: - msg = '\n'.join([ - "Command '{}' failed:".format(module), - "---------- Error log ----------", - log, - "-------------------------------", - ]) + msg = "\n".join( + [ + "Command '{}' failed:".format(module), + "---------- Error log ----------", + log, + "-------------------------------", + ] + ) raise GMTCLibError(msg) def create_data(self, family, geometry, mode, **kwargs): @@ -531,34 +546,39 @@ def create_data(self, family, geometry, mode, **kwargs): """ c_create_data = self.get_libgmt_func( - 'GMT_Create_Data', - argtypes=[ctypes.c_void_p, # API - ctypes.c_uint, # family - ctypes.c_uint, # geometry - ctypes.c_uint, # mode - ctypes.POINTER(ctypes.c_uint64), # dim - ctypes.POINTER(ctypes.c_double), # range - ctypes.POINTER(ctypes.c_double), # inc - ctypes.c_uint, # registration - ctypes.c_int, # pad - ctypes.c_void_p], # data - restype=ctypes.c_void_p) - - family_int = self._parse_constant(family, valid=self.data_families, - valid_modifiers=self.data_vias) - mode_int = self._parse_constant(mode, valid=self.data_modes, - valid_modifiers=["GMT_GRID_IS_GEO"]) - geometry_int = self._parse_constant( - geometry, valid=self.data_geometries) + "GMT_Create_Data", + argtypes=[ + ctypes.c_void_p, # API + ctypes.c_uint, # family + ctypes.c_uint, # geometry + ctypes.c_uint, # mode + ctypes.POINTER(ctypes.c_uint64), # dim + ctypes.POINTER(ctypes.c_double), # range + ctypes.POINTER(ctypes.c_double), # inc + ctypes.c_uint, # registration + ctypes.c_int, # pad + ctypes.c_void_p, + ], # data + restype=ctypes.c_void_p, + ) + + family_int = self._parse_constant( + family, valid=self.data_families, valid_modifiers=self.data_vias + ) + mode_int = self._parse_constant( + mode, valid=self.data_modes, valid_modifiers=["GMT_GRID_IS_GEO"] + ) + geometry_int = self._parse_constant(geometry, valid=self.data_geometries) registration_int = self._parse_constant( - kwargs.get('registration', 'GMT_GRID_NODE_REG'), - valid=self.grid_registrations) + kwargs.get("registration", "GMT_GRID_NODE_REG"), + valid=self.grid_registrations, + ) # Convert dim, ranges, and inc to ctypes arrays if given (will be None # if not given to represent NULL pointers) - dim = kwargs_to_ctypes_array('dim', kwargs, ctypes.c_uint64*4) - ranges = kwargs_to_ctypes_array('ranges', kwargs, ctypes.c_double*4) - inc = kwargs_to_ctypes_array('inc', kwargs, ctypes.c_double*2) + dim = kwargs_to_ctypes_array("dim", kwargs, ctypes.c_uint64 * 4) + ranges = kwargs_to_ctypes_array("ranges", kwargs, ctypes.c_double * 4) + inc = kwargs_to_ctypes_array("inc", kwargs, ctypes.c_double * 2) # Use a NULL pointer (None) for existing data to indicate that the # container should be created empty. Fill it in later using put_vector @@ -573,7 +593,8 @@ def create_data(self, family, geometry, mode, **kwargs): inc, registration_int, self._parse_pad(family, kwargs), - None) + None, + ) if data_ptr is None: raise GMTCLibError("Failed to create an empty GMT data pointer.") @@ -588,12 +609,12 @@ def _parse_pad(self, family, kwargs): ordering (row or column major). Using the default pad will set it to column major and mess things up with the numpy arrays. """ - pad = kwargs.get('pad', None) + pad = kwargs.get("pad", None) if pad is None: - if 'MATRIX' in family: + if "MATRIX" in family: pad = 0 else: - pad = self.get_constant('GMT_PAD_DEFAULT') + pad = self.get_constant("GMT_PAD_DEFAULT") return pad def _parse_constant(self, constant, valid, valid_modifiers=None): @@ -621,26 +642,36 @@ def _parse_constant(self, constant, valid, valid_modifiers=None): :class:`~gmt.exceptions.GMTInvalidInput` exception if the given value is not on the list. """ - parts = constant.split('|') + parts = constant.split("|") name = parts[0] nmodifiers = len(parts) - 1 if nmodifiers > 1: raise GMTInvalidInput( - "Only one modifier is allowed in constants, {} given: '{}'" - .format(nmodifiers, constant)) + "Only one modifier is allowed in constants, {} given: '{}'".format( + nmodifiers, constant + ) + ) if nmodifiers > 0 and valid_modifiers is None: raise GMTInvalidInput( - "Constant modifiers not allowed since valid values were not " + - "given: '{}'".format(constant)) + "Constant modifiers not allowed since valid values were not " + + "given: '{}'".format(constant) + ) if name not in valid: raise GMTInvalidInput( - "Invalid constant argument '{}'. Must be one of {}." - .format(name, str(valid))) - if nmodifiers > 0 and valid_modifiers is not None \ - and parts[1] not in valid_modifiers: + "Invalid constant argument '{}'. Must be one of {}.".format( + name, str(valid) + ) + ) + if ( + nmodifiers > 0 + and valid_modifiers is not None + and parts[1] not in valid_modifiers + ): raise GMTInvalidInput( - "Invalid constant modifier '{}'. Must be one of {}." - .format(parts[1], str(valid_modifiers))) + "Invalid constant modifier '{}'. Must be one of {}.".format( + parts[1], str(valid_modifiers) + ) + ) integer_value = sum(self.get_constant(part) for part in parts) return integer_value @@ -731,21 +762,30 @@ def put_vector(self, dataset, column, vector): """ c_put_vector = self.get_libgmt_func( - 'GMT_Put_Vector', - argtypes=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, - ctypes.c_uint, ctypes.c_void_p], - restype=ctypes.c_int) + "GMT_Put_Vector", + argtypes=[ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_void_p, + ], + restype=ctypes.c_int, + ) gmt_type = self._check_dtype_and_dim(vector, ndim=1) vector_pointer = vector.ctypes.data_as(ctypes.c_void_p) - status = c_put_vector(self.current_session, dataset, column, gmt_type, - vector_pointer) + status = c_put_vector( + self.current_session, dataset, column, gmt_type, vector_pointer + ) if status != 0: raise GMTCLibError( - ' '.join([ - "Failed to put vector of type {}".format(vector.dtype), - "in column {} of dataset.".format(column), - ]) + " ".join( + [ + "Failed to put vector of type {}".format(vector.dtype), + "in column {} of dataset.".format(column), + ] + ) ) def put_matrix(self, dataset, matrix, pad=0): @@ -786,18 +826,24 @@ def put_matrix(self, dataset, matrix, pad=0): """ c_put_matrix = self.get_libgmt_func( - 'GMT_Put_Matrix', - argtypes=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, - ctypes.c_int, ctypes.c_void_p], - restype=ctypes.c_int) + "GMT_Put_Matrix", + argtypes=[ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_int, + ctypes.c_void_p, + ], + restype=ctypes.c_int, + ) gmt_type = self._check_dtype_and_dim(matrix, ndim=2) matrix_pointer = matrix.ctypes.data_as(ctypes.c_void_p) - status = c_put_matrix(self.current_session, dataset, gmt_type, pad, - matrix_pointer) + status = c_put_matrix( + self.current_session, dataset, gmt_type, pad, matrix_pointer + ) if status != 0: - raise GMTCLibError( - "Failed to put matrix of type {}.".format(matrix.dtype)) + raise GMTCLibError("Failed to put matrix of type {}.".format(matrix.dtype)) def write_data(self, family, geometry, mode, wesn, output, data): """ @@ -840,25 +886,36 @@ def write_data(self, family, geometry, mode, wesn, output, data): """ c_write_data = self.get_libgmt_func( - 'GMT_Write_Data', - argtypes=[ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, - ctypes.c_uint, ctypes.c_uint, - ctypes.POINTER(ctypes.c_double), ctypes.c_char_p, - ctypes.c_void_p], - restype=ctypes.c_int) - - family_int = self._parse_constant(family, valid=self.data_families, - valid_modifiers=self.data_vias) - geometry_int = self._parse_constant(geometry, - valid=self.data_geometries) - status = c_write_data(self.current_session, family_int, - self.get_constant('GMT_IS_FILE'), geometry_int, - self.get_constant(mode), - (ctypes.c_double*6)(*wesn), output.encode(), - data) + "GMT_Write_Data", + argtypes=[ + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_double), + ctypes.c_char_p, + ctypes.c_void_p, + ], + restype=ctypes.c_int, + ) + + family_int = self._parse_constant( + family, valid=self.data_families, valid_modifiers=self.data_vias + ) + geometry_int = self._parse_constant(geometry, valid=self.data_geometries) + status = c_write_data( + self.current_session, + family_int, + self.get_constant("GMT_IS_FILE"), + geometry_int, + self.get_constant(mode), + (ctypes.c_double * 6)(*wesn), + output.encode(), + data, + ) if status != 0: - raise GMTCLibError( - "Failed to write dataset to '{}'".format(output)) + raise GMTCLibError("Failed to write dataset to '{}'".format(output)) @contextmanager def open_virtual_file(self, family, geometry, direction, data): @@ -924,28 +981,39 @@ def open_virtual_file(self, family, geometry, direction, data): """ c_open_virtualfile = self.get_libgmt_func( - 'GMT_Open_VirtualFile', - argtypes=[ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, - ctypes.c_uint, ctypes.c_void_p, ctypes.c_char_p], - restype=ctypes.c_int) + "GMT_Open_VirtualFile", + argtypes=[ + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_void_p, + ctypes.c_char_p, + ], + restype=ctypes.c_int, + ) c_close_virtualfile = self.get_libgmt_func( - 'GMT_Close_VirtualFile', + "GMT_Close_VirtualFile", argtypes=[ctypes.c_void_p, ctypes.c_char_p], - restype=ctypes.c_int) + restype=ctypes.c_int, + ) - family_int = self._parse_constant(family, valid=self.data_families, - valid_modifiers=self.data_vias) - geometry_int = self._parse_constant(geometry, - valid=self.data_geometries) + family_int = self._parse_constant( + family, valid=self.data_families, valid_modifiers=self.data_vias + ) + geometry_int = self._parse_constant(geometry, valid=self.data_geometries) direction_int = self._parse_constant( - direction, valid=['GMT_IN', 'GMT_OUT'], - valid_modifiers=['GMT_IS_REFERENCE', 'GMT_IS_DUPLICATE']) + direction, + valid=["GMT_IN", "GMT_OUT"], + valid_modifiers=["GMT_IS_REFERENCE", "GMT_IS_DUPLICATE"], + ) - buff = ctypes.create_string_buffer(self.get_constant('GMT_STR16')) + buff = ctypes.create_string_buffer(self.get_constant("GMT_STR16")) - status = c_open_virtualfile(self.current_session, family_int, - geometry_int, direction_int, data, buff) + status = c_open_virtualfile( + self.current_session, family_int, geometry_int, direction_int, data, buff + ) if status != 0: raise GMTCLibError("Failed to create a virtual file.") @@ -957,8 +1025,7 @@ def open_virtual_file(self, family, geometry, direction, data): finally: status = c_close_virtualfile(self.current_session, vfname.encode()) if status != 0: - raise GMTCLibError( - "Failed to close virtual file '{}'.".format(vfname)) + raise GMTCLibError("Failed to close virtual file '{}'.".format(vfname)) @contextmanager def vectors_to_vfile(self, *vectors): @@ -1025,16 +1092,17 @@ def vectors_to_vfile(self, *vectors): if not all(len(i) == rows for i in arrays): raise GMTInvalidInput("All arrays must have same size.") - family = 'GMT_IS_DATASET|GMT_VIA_VECTOR' - geometry = 'GMT_IS_POINT' + family = "GMT_IS_DATASET|GMT_VIA_VECTOR" + geometry = "GMT_IS_POINT" - dataset = self.create_data(family, geometry, mode='GMT_CONTAINER_ONLY', - dim=[columns, rows, 1, 0]) + dataset = self.create_data( + family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] + ) for col, array in enumerate(arrays): self.put_vector(dataset, column=col, vector=array) - vf_args = (family, geometry, 'GMT_IN', dataset) + vf_args = (family, geometry, "GMT_IN", dataset) with self.open_virtual_file(*vf_args) as vfile: yield vfile @@ -1106,15 +1174,16 @@ def matrix_to_vfile(self, matrix): matrix = as_c_contiguous(matrix) rows, columns = matrix.shape - family = 'GMT_IS_DATASET|GMT_VIA_MATRIX' - geometry = 'GMT_IS_POINT' + family = "GMT_IS_DATASET|GMT_VIA_MATRIX" + geometry = "GMT_IS_POINT" - dataset = self.create_data(family, geometry, mode='GMT_CONTAINER_ONLY', - dim=[columns, rows, 1, 0]) + dataset = self.create_data( + family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] + ) self.put_matrix(dataset, matrix) - vf_args = (family, geometry, 'GMT_IN', dataset) + vf_args = (family, geometry, "GMT_IN", dataset) with self.open_virtual_file(*vf_args) as vfile: yield vfile @@ -1185,13 +1254,13 @@ def grid_to_vfile(self, grid): # closed. # The conversion is implicit in dataarray_to_matrix. matrix, region, inc = dataarray_to_matrix(grid) - family = 'GMT_IS_GRID|GMT_VIA_MATRIX' - geometry = 'GMT_IS_SURFACE' - gmt_grid = self.create_data(family, geometry, - mode='GMT_CONTAINER_ONLY', - ranges=region, inc=inc) + family = "GMT_IS_GRID|GMT_VIA_MATRIX" + geometry = "GMT_IS_SURFACE" + gmt_grid = self.create_data( + family, geometry, mode="GMT_CONTAINER_ONLY", ranges=region, inc=inc + ) self.put_matrix(gmt_grid, matrix) - args = (family, geometry, 'GMT_IN|GMT_IS_REFERENCE', gmt_grid) + args = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", gmt_grid) with self.open_virtual_file(*args) as vfile: yield vfile @@ -1245,10 +1314,14 @@ def extract_region(self): """ c_extract_region = self.get_libgmt_func( - 'GMT_Extract_Region', - argtypes=[ctypes.c_void_p, ctypes.c_char_p, - ctypes.POINTER(ctypes.c_double)], - restype=ctypes.c_int) + "GMT_Extract_Region", + argtypes=[ + ctypes.c_void_p, + ctypes.c_char_p, + ctypes.POINTER(ctypes.c_double), + ], + restype=ctypes.c_int, + ) wesn = np.empty(4, dtype=np.float64) # Use NaNs so that we can know if GMT didn't change the array diff --git a/gmt/clib/utils.py b/gmt/clib/utils.py index 7d8cfbb47e7..e8fb8bb5f39 100644 --- a/gmt/clib/utils.py +++ b/gmt/clib/utils.py @@ -8,8 +8,7 @@ import numpy as np import pandas -from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError, \ - GMTInvalidInput +from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError, GMTInvalidInput def dataarray_to_matrix(grid): @@ -87,8 +86,8 @@ def dataarray_to_matrix(grid): """ if len(grid.dims) != 2: raise GMTInvalidInput( - "Invalid number of grid dimensions '{}'. Must be 2." - .format(len(grid.dims))) + "Invalid number of grid dimensions '{}'. Must be 2.".format(len(grid.dims)) + ) # Extract region and inc from the grid region = [] inc = [] @@ -101,8 +100,10 @@ def dataarray_to_matrix(grid): coord_inc = coord_incs[0] if not np.allclose(coord_incs, coord_inc): raise GMTInvalidInput( - "Grid appears to have irregular spacing in the '{}' dimension." - .format(dim)) + "Grid appears to have irregular spacing in the '{}' dimension.".format( + dim + ) + ) region.extend([coord.min(), coord.max()]) inc.append(coord_inc) matrix = as_c_contiguous(grid.values[::-1]) @@ -195,7 +196,7 @@ def as_c_contiguous(array): """ if not array.flags.c_contiguous: - return array.copy(order='C') + return array.copy(order="C") return array @@ -272,11 +273,13 @@ def load_libgmt(env=None): libgmt = ctypes.CDLL(libpath) check_libgmt(libgmt) except OSError as err: - msg = '\n'.join([ - "Couldn't find the GMT shared library '{}'.".format(libpath), - "Original error message:", - "{}".format(str(err)), - ]) + msg = "\n".join( + [ + "Couldn't find the GMT shared library '{}'.".format(libpath), + "Original error message:", + "{}".format(str(err)), + ] + ) raise GMTCLibNotFoundError(msg) return libgmt @@ -300,11 +303,11 @@ def get_clib_path(env): The path to the libgmt shared library. """ - libname = '.'.join(['libgmt', clib_extension()]) + libname = ".".join(["libgmt", clib_extension()]) if env is None: env = os.environ - if 'GMT_LIBRARY_PATH' in env: - libpath = os.path.join(env['GMT_LIBRARY_PATH'], libname) + if "GMT_LIBRARY_PATH" in env: + libpath = os.path.join(env["GMT_LIBRARY_PATH"], libname) else: libpath = libname return libpath @@ -333,14 +336,13 @@ def clib_extension(os_name=None): if os_name is None: os_name = sys.platform # Set the shared library extension in a platform independent way - if os_name.startswith('linux'): - lib_ext = 'so' - elif os_name == 'darwin': + if os_name.startswith("linux"): + lib_ext = "so" + elif os_name == "darwin": # Darwin is macOS - lib_ext = 'dylib' + lib_ext = "dylib" else: - raise GMTOSError( - 'Operating system "{}" not supported.'.format(sys.platform)) + raise GMTOSError('Operating system "{}" not supported.'.format(sys.platform)) return lib_ext @@ -364,14 +366,15 @@ def check_libgmt(libgmt): """ # Check if a few of the functions we need are in the library - functions = ['Create_Session', 'Get_Enum', 'Call_Module', - 'Destroy_Session'] + functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"] for func in functions: - if not hasattr(libgmt, 'GMT_' + func): - msg = ' '.join([ - "Error loading libgmt.", - "Couldn't access function GMT_{}.".format(func), - ]) + if not hasattr(libgmt, "GMT_" + func): + msg = " ".join( + [ + "Error loading libgmt.", + "Couldn't access function GMT_{}.".format(func), + ] + ) raise GMTCLibError(msg) diff --git a/gmt/datasets/earth_relief.py b/gmt/datasets/earth_relief.py index 8df1c339601..dcca95d2b8c 100644 --- a/gmt/datasets/earth_relief.py +++ b/gmt/datasets/earth_relief.py @@ -8,7 +8,7 @@ from ..exceptions import GMTInvalidInput -def load_earth_relief(resolution='60m'): +def load_earth_relief(resolution="60m"): """ Load Earth relief grids (topography and bathymetry) in various resolutions. @@ -34,12 +34,12 @@ def load_earth_relief(resolution='60m'): degrees. Relief is in meters. """ - valid_resolutions = ['{:02d}m'.format(res) - for res in [60, 30, 10, 5, 2, 1]] + valid_resolutions = ["{:02d}m".format(res) for res in [60, 30, 10, 5, 2, 1]] if resolution not in valid_resolutions: - raise GMTInvalidInput("Invalid Earth relief resolution '{}'." - .format(resolution)) - fname = which('@earth_relief_{}'.format(resolution), download='u') + raise GMTInvalidInput( + "Invalid Earth relief resolution '{}'.".format(resolution) + ) + fname = which("@earth_relief_{}".format(resolution), download="u") grid = xr.open_dataarray(fname) return grid @@ -70,6 +70,6 @@ def _shape_from_resolution(resolution): """ minutes = int(resolution[:2]) - nlat = 180*60//minutes + 1 - nlon = 360*60//minutes + 1 + nlat = 180 * 60 // minutes + 1 + nlon = 360 * 60 // minutes + 1 return (nlat, nlon) diff --git a/gmt/datasets/tutorial.py b/gmt/datasets/tutorial.py index 0a78e6247aa..8f38c756885 100644 --- a/gmt/datasets/tutorial.py +++ b/gmt/datasets/tutorial.py @@ -24,8 +24,15 @@ def load_japan_quakes(): depth (in km), and magnitude of the earthquakes. """ - fname = which('@tut_quakes.ngdc', download='c') - data = pd.read_table(fname, header=1, sep=r'\s+') - data.columns = ['year', 'month', 'day', 'latitude', 'longitude', - 'depth_km', 'magnitude'] + fname = which("@tut_quakes.ngdc", download="c") + data = pd.read_table(fname, header=1, sep=r"\s+") + data.columns = [ + "year", + "month", + "day", + "latitude", + "longitude", + "depth_km", + "magnitude", + ] return data diff --git a/gmt/exceptions.py b/gmt/exceptions.py index 5b78c026720..4737ad42fe8 100644 --- a/gmt/exceptions.py +++ b/gmt/exceptions.py @@ -9,6 +9,7 @@ class GMTError(Exception): """ Base class for all GMT related errors. """ + pass @@ -16,6 +17,7 @@ class GMTOSError(GMTError): """ Unsupported operating system. """ + pass @@ -23,6 +25,7 @@ class GMTCLibError(GMTError): """ Error encountered when running a function from the GMT shared library. """ + pass @@ -30,6 +33,7 @@ class GMTCLibNotFoundError(GMTCLibError): """ Could not find the GMT shared library. """ + pass @@ -37,6 +41,7 @@ class GMTCLibNoSessionError(GMTCLibError): """ Tried to access GMT API without a currently open GMT session. """ + pass @@ -44,6 +49,7 @@ class GMTInvalidInput(GMTError): """ Raised when the input of a function/method is invalid. """ + pass @@ -51,4 +57,5 @@ class GMTVersionError(GMTError): """ Raised when an incompatible version of GMT is being used. """ + pass diff --git a/gmt/figure.py b/gmt/figure.py index 87da99c7ee0..82db15842ec 100644 --- a/gmt/figure.py +++ b/gmt/figure.py @@ -13,8 +13,15 @@ from .clib import LibGMT from .base_plotting import BasePlotting from .exceptions import GMTError, GMTInvalidInput -from .helpers import build_arg_string, fmt_docstring, use_alias, \ - kwargs_to_strings, launch_external_viewer, unique_name, worldwind_show +from .helpers import ( + build_arg_string, + fmt_docstring, + use_alias, + kwargs_to_strings, + launch_external_viewer, + unique_name, + worldwind_show, +) class Figure(BasePlotting): @@ -53,12 +60,12 @@ class Figure(BasePlotting): def __init__(self): self._name = unique_name() - self._preview_dir = TemporaryDirectory(prefix=self._name + '-preview-') + self._preview_dir = TemporaryDirectory(prefix=self._name + "-preview-") self._activate_figure() def __del__(self): # Clean up the temporary directory that stores the previews - if hasattr(self, '_preview_dir'): + if hasattr(self, "_preview_dir"): self._preview_dir.cleanup() def _activate_figure(self): @@ -73,9 +80,9 @@ def _activate_figure(self): in order to get a file. """ # Passing format '-' tells gmt.end to not produce any files. - fmt = '-' + fmt = "-" with LibGMT() as lib: - lib.call_module('figure', '{} {}'.format(self._name, fmt)) + lib.call_module("figure", "{} {}".format(self._name, fmt)) def _preprocess(self, **kwargs): """ @@ -94,7 +101,7 @@ def region(self): return wesn @fmt_docstring - @use_alias(F='prefix', T='fmt', A='crop', E='dpi') + @use_alias(F="prefix", T="fmt", A="crop", E="dpi") @kwargs_to_strings() def psconvert(self, **kwargs): """ @@ -148,13 +155,14 @@ def psconvert(self, **kwargs): """ kwargs = self._preprocess(**kwargs) # Default cropping the figure to True - if 'A' not in kwargs: - kwargs['A'] = '' + if "A" not in kwargs: + kwargs["A"] = "" with LibGMT() as lib: - lib.call_module('psconvert', build_arg_string(kwargs)) + lib.call_module("psconvert", build_arg_string(kwargs)) - def savefig(self, fname, transparent=False, crop=True, anti_alias=True, - show=False, **kwargs): + def savefig( + self, fname, transparent=False, crop=True, anti_alias=True, show=False, **kwargs + ): """ Save the figure to a file. @@ -191,8 +199,7 @@ def savefig(self, fname, transparent=False, crop=True, anti_alias=True, """ # All supported formats - fmts = dict(png='g', pdf='f', jpg='j', bmp='b', eps='e', tif='t', - kml='g') + fmts = dict(png="g", pdf="f", jpg="j", bmp="b", eps="e", tif="t", kml="g") prefix, ext = os.path.splitext(fname) ext = ext[1:] # Remove the . @@ -200,22 +207,22 @@ def savefig(self, fname, transparent=False, crop=True, anti_alias=True, raise GMTInvalidInput("Unknown extension '.{}'".format(ext)) fmt = fmts[ext] if transparent: - if fmt != 'g': + if fmt != "g": raise GMTInvalidInput( - "Transparency unavailable for '{}', only for png." - .format(ext)) + "Transparency unavailable for '{}', only for png.".format(ext) + ) fmt = fmt.upper() if anti_alias: - kwargs['Qt'] = 2 - kwargs['Qg'] = 2 - if ext == 'kml': - kwargs['W'] = '+k' + kwargs["Qt"] = 2 + kwargs["Qg"] = 2 + if ext == "kml": + kwargs["W"] = "+k" self.psconvert(prefix=prefix, fmt=fmt, crop=crop, **kwargs) if show: launch_external_viewer(fname) - def show(self, dpi=300, width=500, method='static', globe_center=None): + def show(self, dpi=300, width=500, method="static", globe_center=None): """ Display a preview of the figure. @@ -256,27 +263,35 @@ def show(self, dpi=300, width=500, method='static', globe_center=None): Only if ``method != 'external'``. """ - if method not in ['static', 'external', 'globe']: + if method not in ["static", "external", "globe"]: raise GMTInvalidInput("Invalid show method '{}'.".format(method)) - if method == 'globe': - png = self._preview(fmt='png', dpi=dpi, anti_alias=True, - as_bytes=True, transparent=True) - img = worldwind_show(image=png, width=width, region=self.region, - canvas_id=self._name, - globe_center=globe_center) - elif method == 'external': - pdf = self._preview(fmt='pdf', dpi=600, anti_alias=False, - as_bytes=False) + if method == "globe": + png = self._preview( + fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True + ) + img = worldwind_show( + image=png, + width=width, + region=self.region, + canvas_id=self._name, + globe_center=globe_center, + ) + elif method == "external": + pdf = self._preview(fmt="pdf", dpi=600, anti_alias=False, as_bytes=False) launch_external_viewer(pdf) img = None - elif method == 'static': - png = self._preview(fmt='png', dpi=dpi, anti_alias=True, - as_bytes=True) + elif method == "static": + png = self._preview(fmt="png", dpi=dpi, anti_alias=True, as_bytes=True) if Image is None: - raise GMTError(' '.join([ - "Cannot find IPython.", - "Make sure you have it installed", - "or use 'external=True' to open in an external viewer."])) + raise GMTError( + " ".join( + [ + "Cannot find IPython.", + "Make sure you have it installed", + "or use 'external=True' to open in an external viewer.", + ] + ) + ) img = Image(data=png, width=width) return img @@ -302,11 +317,10 @@ def _preview(self, fmt, dpi, as_bytes=False, **kwargs): file. Else, it is the file content loaded as a bytes string. """ - fname = os.path.join(self._preview_dir.name, - '{}.{}'.format(self._name, fmt)) + fname = os.path.join(self._preview_dir.name, "{}.{}".format(self._name, fmt)) self.savefig(fname, dpi=dpi, **kwargs) if as_bytes: - with open(fname, 'rb') as image: + with open(fname, "rb") as image: preview = image.read() return preview return fname @@ -316,7 +330,7 @@ def _repr_png_(self): Show a PNG preview if the object is returned in an interactive shell. For the Jupyter notebook or IPython Qt console. """ - png = self._preview(fmt='png', dpi=70, anti_alias=True, as_bytes=True) + png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True) return png def _repr_html_(self): @@ -324,8 +338,7 @@ def _repr_html_(self): Show the PNG image embedded in HTML with a controlled width. Looks better than the raw PNG. """ - raw_png = self._preview(fmt='png', dpi=300, anti_alias=True, - as_bytes=True) + raw_png = self._preview(fmt="png", dpi=300, anti_alias=True, as_bytes=True) base64_png = base64.encodebytes(raw_png) html = '' - return html.format(image=base64_png.decode('utf-8'), width=500) + return html.format(image=base64_png.decode("utf-8"), width=500) diff --git a/gmt/helpers/__init__.py b/gmt/helpers/__init__.py index 86db7dd3132..399316fc726 100644 --- a/gmt/helpers/__init__.py +++ b/gmt/helpers/__init__.py @@ -3,6 +3,11 @@ """ from .decorators import fmt_docstring, use_alias, kwargs_to_strings from .tempfile import GMTTempFile, unique_name -from .utils import data_kind, dummy_context, build_arg_string, \ - is_nonstr_iter, launch_external_viewer +from .utils import ( + data_kind, + dummy_context, + build_arg_string, + is_nonstr_iter, + launch_external_viewer, +) from .worldwind import worldwind_show diff --git a/gmt/helpers/decorators.py b/gmt/helpers/decorators.py index 82bf31adf30..6058438c4de 100644 --- a/gmt/helpers/decorators.py +++ b/gmt/helpers/decorators.py @@ -12,38 +12,38 @@ from ..exceptions import GMTInvalidInput -GMT_DOCS = 'http://gmt.soest.hawaii.edu/doc/latest' +GMT_DOCS = "http://gmt.soest.hawaii.edu/doc/latest" COMMON_OPTIONS = { - 'R': '''\ + "R": """\ R : str or list *Required if this is the first plot command*. ``'xmin/xmax/ymin/ymax[+r][+uunit]'``. - Specify the region of interest.''', - 'J': '''\ + Specify the region of interest.""", + "J": """\ J : str *Required if this is the first plot command*. - Select map projection.''', - 'B': '''\ + Select map projection.""", + "B": """\ B : str - Set map boundary frame and axes attributes.''', - 'P': '''\ + Set map boundary frame and axes attributes.""", + "P": """\ P : bool - Select “Portrait” plot orientation.''', - 'U': '''\ + Select “Portrait” plot orientation.""", + "U": """\ U : bool or str - Draw GMT time stamp logo on plot.''', - 'CPT': '''\ + Draw GMT time stamp logo on plot.""", + "CPT": """\ C : str File name of a CPT file or ``C='color1,color2[,color3,...]'`` to - build a linear continuous CPT from those colors automatically.''', - 'G': '''\ + build a linear continuous CPT from those colors automatically.""", + "G": """\ G : str Select color or pattern for filling of symbols or polygons. Default - is no fill.''', - 'W': '''\ + is no fill.""", + "W": """\ W : str - Set pen attributes for lines or the outline of symbols.''', + Set pen attributes for lines or the outline of symbols.""", } @@ -127,14 +127,14 @@ def fmt_docstring(module_func): url = "{}/{}.html".format(GMT_DOCS, module_func.__name__) text = "Full option list at" - filler_text['gmt_module_docs'] = ' '.join([text, url]) + filler_text["gmt_module_docs"] = " ".join([text, url]) - if hasattr(module_func, 'aliases'): - aliases = ['**Aliases:**\n'] + if hasattr(module_func, "aliases"): + aliases = ["**Aliases:**\n"] for arg in sorted(module_func.aliases): alias = module_func.aliases[arg] - aliases.append('- {} = {}'.format(arg, alias)) - filler_text['aliases'] = '\n'.join(aliases) + aliases.append("- {} = {}".format(arg, alias)) + filler_text["aliases"] = "\n".join(aliases) for marker, text in COMMON_OPTIONS.items(): # Remove the identation from the multiline strings so that it doesn't @@ -262,15 +262,15 @@ def kwargs_to_strings(convert_bools=True, **conversions): args: 123 """ - valid_conversions = ['sequence', 'sequence_comma'] + valid_conversions = ["sequence", "sequence_comma"] for arg, fmt in conversions.items(): if fmt not in valid_conversions: raise GMTInvalidInput( - "Invalid conversion type '{}' for argument '{}'." - .format(fmt, arg)) + "Invalid conversion type '{}' for argument '{}'.".format(fmt, arg) + ) - separators = {'sequence': '/', 'sequence_comma': ','} + separators = {"sequence": "/", "sequence_comma": ","} # Make the actual decorator function def converter(module_func): @@ -284,10 +284,11 @@ def new_module(*args, **kwargs): for arg, fmt in conversions.items(): if arg in kwargs: value = kwargs[arg] - issequence = fmt == 'sequence' or fmt == 'sequence_comma' + issequence = fmt == "sequence" or fmt == "sequence_comma" if issequence and is_nonstr_iter(value): kwargs[arg] = separators[fmt].join( - '{}'.format(item) for item in value) + "{}".format(item) for item in value + ) # Execute the original function and return its output return module_func(*args, **kwargs) @@ -318,7 +319,7 @@ def remove_bools(kwargs): for arg, value in kwargs.items(): if isinstance(value, bool): if value: - new_kwargs[arg] = '' + new_kwargs[arg] = "" else: new_kwargs[arg] = value return new_kwargs diff --git a/gmt/helpers/tempfile.py b/gmt/helpers/tempfile.py index d01202ee00d..036de1e6d48 100644 --- a/gmt/helpers/tempfile.py +++ b/gmt/helpers/tempfile.py @@ -21,11 +21,11 @@ def unique_name(): """ # Use the tempfile module to generate a unique name. - with NamedTemporaryFile(prefix='gmt-python-') as tmpfile: + with NamedTemporaryFile(prefix="gmt-python-") as tmpfile: return os.path.split(tmpfile.name)[-1] -class GMTTempFile(): +class GMTTempFile: """ Context manager for creating closed temporary files. @@ -57,6 +57,7 @@ class GMTTempFile(): [0. 0. 0.] [1. 1. 1.] [2. 2. 2.] """ + def __init__(self, prefix="gmt-python-", suffix=".txt"): args = dict(prefix=prefix, suffix=suffix, delete=False) with NamedTemporaryFile(**args) as tmpfile: @@ -86,7 +87,7 @@ def read(self, keep_tabs=False): with open(self.name) as tmpfile: content = tmpfile.read() if not keep_tabs: - content = content.replace('\t', ' ') + content = content.replace("\t", " ") return content def loadtxt(self, **kwargs): diff --git a/gmt/helpers/utils.py b/gmt/helpers/utils.py index 413aed59acf..3dcda345491 100644 --- a/gmt/helpers/utils.py +++ b/gmt/helpers/utils.py @@ -66,20 +66,20 @@ def data_kind(data, x=None, y=None): """ if data is None and x is None and y is None: - raise GMTInvalidInput('No input data provided.') + raise GMTInvalidInput("No input data provided.") if data is not None and (x is not None or y is not None): - raise GMTInvalidInput('Too much data. Use either data or x and y.') + raise GMTInvalidInput("Too much data. Use either data or x and y.") if data is None and (x is None or y is None): - raise GMTInvalidInput('Must provided both x and y.') + raise GMTInvalidInput("Must provided both x and y.") if isinstance(data, str): - kind = 'file' + kind = "file" elif isinstance(data, xr.DataArray): - kind = 'grid' + kind = "grid" elif data is not None: - kind = 'matrix' + kind = "matrix" else: - kind = 'vectors' + kind = "vectors" return kind @@ -147,11 +147,11 @@ def build_arg_string(kwargs): for key in sorted(kwargs): if is_nonstr_iter(kwargs[key]): for value in kwargs[key]: - sorted_args.append('-{}{}'.format(key, value)) + sorted_args.append("-{}{}".format(key, value)) else: - sorted_args.append('-{}{}'.format(key, kwargs[key])) + sorted_args.append("-{}{}".format(key, kwargs[key])) - arg_str = ' '.join(sorted_args) + arg_str = " ".join(sorted_args) return arg_str @@ -209,9 +209,9 @@ def launch_external_viewer(fname): # Open the file with the default viewer. # Fall back to the browser if can't recognize the operating system. - if sys.platform.startswith('linux') and shutil.which('xdg-open'): - subprocess.run(['xdg-open', fname], **run_args) - elif sys.platform == 'darwin': # Darwin is macOS - subprocess.run(['open', fname], **run_args) + if sys.platform.startswith("linux") and shutil.which("xdg-open"): + subprocess.run(["xdg-open", fname], **run_args) + elif sys.platform == "darwin": # Darwin is macOS + subprocess.run(["open", fname], **run_args) else: - webbrowser.open_new_tab('file://{}'.format(fname)) + webbrowser.open_new_tab("file://{}".format(fname)) diff --git a/gmt/helpers/worldwind.py b/gmt/helpers/worldwind.py index c259ba791d5..4744770bca3 100644 --- a/gmt/helpers/worldwind.py +++ b/gmt/helpers/worldwind.py @@ -94,15 +94,21 @@ def worldwind_show(image, width, region, canvas_id, globe_center): """ if globe_center is None: - height = 200000*max(region[1] - region[0], region[3] - region[2]) + height = 200000 * max(region[1] - region[0], region[3] - region[2]) lon = np.mean(region[:2]) lat = np.mean(region[2:]) globe_center = (lon, lat, height) # Need to escape the newlines so that they can be inserted in the # Javascript - data = base64.encodebytes(image).decode('utf-8').encode('unicode_escape') - b64image = "data:image/png;base64,{}".format(data.decode('utf-8')) - worldwind = TEMPLATE.format(url=URL, canvas=canvas_id, width=width, - height=width, region=region, image=b64image, - center=globe_center) + data = base64.encodebytes(image).decode("utf-8").encode("unicode_escape") + b64image = "data:image/png;base64,{}".format(data.decode("utf-8")) + worldwind = TEMPLATE.format( + url=URL, + canvas=canvas_id, + width=width, + height=width, + region=region, + image=b64image, + center=globe_center, + ) return HTML(data=worldwind) diff --git a/gmt/modules.py b/gmt/modules.py index 1a255edb883..c65ce460948 100644 --- a/gmt/modules.py +++ b/gmt/modules.py @@ -2,8 +2,14 @@ Non-plot GMT modules. """ from .clib import LibGMT -from .helpers import build_arg_string, fmt_docstring, GMTTempFile, use_alias, \ - data_kind, dummy_context +from .helpers import ( + build_arg_string, + fmt_docstring, + GMTTempFile, + use_alias, + data_kind, + dummy_context, +) from .exceptions import GMTInvalidInput @@ -30,17 +36,17 @@ def grdinfo(grid, **kwargs): kind = data_kind(grid, None, None) with GMTTempFile() as outfile: with LibGMT() as lib: - if kind == 'file': + if kind == "file": file_context = dummy_context(grid) - elif kind == 'grid': + elif kind == "grid": file_context = lib.grid_to_vfile(grid) else: - raise GMTInvalidInput("Unrecognized data type: {}" - .format(type(grid))) + raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid))) with file_context as infile: - arg_str = ' '.join([infile, build_arg_string(kwargs), - "->" + outfile.name]) - lib.call_module('grdinfo', arg_str) + arg_str = " ".join( + [infile, build_arg_string(kwargs), "->" + outfile.name] + ) + lib.call_module("grdinfo", arg_str) result = outfile.read() return result @@ -81,15 +87,14 @@ def info(fname, **kwargs): raise GMTInvalidInput("'info' only accepts file names.") with GMTTempFile() as tmpfile: - arg_str = ' '.join([fname, build_arg_string(kwargs), - "->" + tmpfile.name]) + arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) with LibGMT() as lib: - lib.call_module('info', arg_str) + lib.call_module("info", arg_str) return tmpfile.read() @fmt_docstring -@use_alias(G='download') +@use_alias(G="download") def which(fname, **kwargs): """ Find the full path to specified files. @@ -130,10 +135,9 @@ def which(fname, **kwargs): """ with GMTTempFile() as tmpfile: - arg_str = ' '.join([fname, build_arg_string(kwargs), - "->" + tmpfile.name]) + arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) with LibGMT() as lib: - lib.call_module('which', arg_str) + lib.call_module("which", arg_str) path = tmpfile.read().strip() if not path: raise FileNotFoundError("File '{}' not found.".format(fname)) diff --git a/gmt/session_management.py b/gmt/session_management.py index 0fde7359968..02e21cef402 100644 --- a/gmt/session_management.py +++ b/gmt/session_management.py @@ -13,9 +13,9 @@ def begin(): Only meant to be used once for creating the global session. """ - prefix = 'gmt-python-session' + prefix = "gmt-python-session" with LibGMT() as lib: - lib.call_module('begin', prefix) + lib.call_module("begin", prefix) def end(): @@ -29,4 +29,4 @@ def end(): """ with LibGMT() as lib: - lib.call_module('end', '') + lib.call_module("end", "") diff --git a/gmt/tests/test_basemap.py b/gmt/tests/test_basemap.py index 72dc58d06ea..95ad2a7889e 100644 --- a/gmt/tests/test_basemap.py +++ b/gmt/tests/test_basemap.py @@ -11,15 +11,14 @@ def test_basemap_required_args(): "fig.basemap fails when not given required arguments" fig = Figure() with pytest.raises(GMTInvalidInput): - fig.basemap(R='10/70/-3/8', J='X4i/3i') + fig.basemap(R="10/70/-3/8", J="X4i/3i") @pytest.mark.mpl_image_compare def test_basemap_d(): "Make sure the D option works" fig = Figure() - fig.basemap(R='10/70/-300/800', J='X3i/5i', B='af', D='30/35/-200/500', - F=True) + fig.basemap(R="10/70/-300/800", J="X3i/5i", B="af", D="30/35/-200/500", F=True) return fig @@ -27,14 +26,14 @@ def test_basemap_d_raises(): "Make sure the D raises an error when F not given." fig = Figure() with pytest.raises(GMTInvalidInput): - fig.basemap(R='10/70/-300/800', J='X3i/5i', B='af', D='30/35/-200/500') + fig.basemap(R="10/70/-300/800", J="X3i/5i", B="af", D="30/35/-200/500") @pytest.mark.mpl_image_compare def test_basemap(): "Create a simple basemap plot" fig = Figure() - fig.basemap(R='10/70/-3/8', J='X4i/3i', B='afg') + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="afg") return fig @@ -42,7 +41,7 @@ def test_basemap(): def test_basemap_list_region(): "Create a simple basemap plot passing the region as a list" fig = Figure() - fig.basemap(R=[-20, 50, 200, 500], J='X3i/3i', B='a') + fig.basemap(R=[-20, 50, 200, 500], J="X3i/3i", B="a") return fig @@ -50,8 +49,13 @@ def test_basemap_list_region(): def test_basemap_loglog(): "Create a loglog basemap plot" fig = Figure() - fig.basemap(R='1/10000/1e20/1e25', J='X25cl/15cl', Bx='2+lWavelength', - By='a1pf3+lPower', B='WS') + fig.basemap( + R="1/10000/1e20/1e25", + J="X25cl/15cl", + Bx="2+lWavelength", + By="a1pf3+lPower", + B="WS", + ) return fig @@ -59,8 +63,9 @@ def test_basemap_loglog(): def test_basemap_power_axis(): "Create a power axis basemap plot" fig = Figure() - fig.basemap(R=[0, 100, 0, 5000], J='x1p0.5/-0.001', - B='x1p+l"Crustal age"y500+lDepth') + fig.basemap( + R=[0, 100, 0, 5000], J="x1p0.5/-0.001", B='x1p+l"Crustal age"y500+lDepth' + ) return fig @@ -68,7 +73,7 @@ def test_basemap_power_axis(): def test_basemap_polar(): "Create a polar basemap plot" fig = Figure() - fig.basemap(R='0/360/0/1000', J='P6i', B='afg') + fig.basemap(R="0/360/0/1000", J="P6i", B="afg") return fig @@ -76,7 +81,7 @@ def test_basemap_polar(): def test_basemap_winkel_tripel(): "Create a Winkel Tripel basemap plot" fig = Figure() - fig.basemap(R='90/450/-90/90', J='R270/25c', B='afg') + fig.basemap(R="90/450/-90/90", J="R270/25c", B="afg") return fig @@ -84,5 +89,5 @@ def test_basemap_winkel_tripel(): def test_basemap_aliases(): "Make sure the argument aliases work" fig = Figure() - fig.basemap(region=[0, 360, -90, 90], projection='W7i', frame=True) + fig.basemap(region=[0, 360, -90, 90], projection="W7i", frame=True) return fig diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index 1b0d5ea5899..99a29bf5c25 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -13,15 +13,26 @@ from packaging.version import Version from ..clib.core import LibGMT -from ..clib.utils import clib_extension, load_libgmt, check_libgmt, \ - dataarray_to_matrix, get_clib_path -from ..exceptions import GMTCLibError, GMTOSError, GMTCLibNotFoundError, \ - GMTCLibNoSessionError, GMTInvalidInput, GMTVersionError +from ..clib.utils import ( + clib_extension, + load_libgmt, + check_libgmt, + dataarray_to_matrix, + get_clib_path, +) +from ..exceptions import ( + GMTCLibError, + GMTOSError, + GMTCLibNotFoundError, + GMTCLibNoSessionError, + GMTInvalidInput, + GMTVersionError, +) from ..helpers import GMTTempFile from .. import Figure -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @contextmanager @@ -56,7 +67,7 @@ def mock_get_libgmt_func(name, argtypes=None, restype=None): return mock_func return get_libgmt_func(name, argtypes, restype) - setattr(lib, 'get_libgmt_func', mock_get_libgmt_func) + setattr(lib, "get_libgmt_func", mock_get_libgmt_func) yield @@ -68,7 +79,7 @@ def test_load_libgmt(): def test_load_libgmt_fail(): "Test that loading fails when given a bad library path." - env = {'GMT_LIBRARY_PATH': 'not/a/real/path'} + env = {"GMT_LIBRARY_PATH": "not/a/real/path"} with pytest.raises(GMTCLibNotFoundError): load_libgmt(env=env) @@ -77,11 +88,11 @@ def test_get_clib_path(): "Test that the correct path is found when setting GMT_LIBRARY_PATH." # Get the real path to the library first with LibGMT() as lib: - libpath = lib.info['library path'] + libpath = lib.info["library path"] libdir = os.path.dirname(libpath) # Assign it to the environment variable but keep a backup value to restore # later - env = {'GMT_LIBRARY_PATH': libdir} + env = {"GMT_LIBRARY_PATH": libdir} # Check that the path is determined correctly path_used = get_clib_path(env=env) @@ -100,30 +111,30 @@ def test_check_libgmt(): def test_clib_extension(): "Make sure we get the correct extension for different OS names" - for linux in ['linux', 'linux2', 'linux3']: - assert clib_extension(linux) == 'so' - assert clib_extension('darwin') == 'dylib' + for linux in ["linux", "linux2", "linux3"]: + assert clib_extension(linux) == "so" + assert clib_extension("darwin") == "dylib" with pytest.raises(GMTOSError): - clib_extension('meh') + clib_extension("meh") def test_constant(): "Test that I can get correct constants from the C lib" lib = LibGMT() - assert lib.get_constant('GMT_SESSION_EXTERNAL') != -99999 - assert lib.get_constant('GMT_MODULE_CMD') != -99999 - assert lib.get_constant('GMT_PAD_DEFAULT') != -99999 - assert lib.get_constant('GMT_DOUBLE') != -99999 + assert lib.get_constant("GMT_SESSION_EXTERNAL") != -99999 + assert lib.get_constant("GMT_MODULE_CMD") != -99999 + assert lib.get_constant("GMT_PAD_DEFAULT") != -99999 + assert lib.get_constant("GMT_DOUBLE") != -99999 with pytest.raises(GMTCLibError): - lib.get_constant('A_WHOLE_LOT_OF_JUNK') + lib.get_constant("A_WHOLE_LOT_OF_JUNK") def test_create_destroy_session(): "Test that create and destroy session are called without errors" lib = LibGMT() - session1 = lib.create_session(session_name='test_session1') + session1 = lib.create_session(session_name="test_session1") assert session1 is not None - session2 = lib.create_session(session_name='test_session2') + session2 = lib.create_session(session_name="test_session2") assert session2 is not None assert session2 != session1 lib.destroy_session(session1) @@ -133,9 +144,9 @@ def test_create_destroy_session(): def test_create_session_fails(): "Check that an exception is raised if the session pointer is None" lib = LibGMT() - with mock(lib, 'GMT_Create_Session', returns=None): + with mock(lib, "GMT_Create_Session", returns=None): with pytest.raises(GMTCLibError): - lib.create_session('test-session-name') + lib.create_session("test-session-name") def test_destroy_session_fails(): @@ -149,7 +160,7 @@ def test_set_log_file_fails(): "log_to_file should fail for invalid file names" with LibGMT() as lib: with pytest.raises(GMTCLibError): - with lib.log_to_file(logfile=''): + with lib.log_to_file(logfile=""): print("This should have failed") @@ -160,14 +171,14 @@ def logged_call_module(lib, data_file): Checks that the call results in an error and that the correct error message is logged. """ - msg = 'gmtinfo [ERROR]: Error for input file: No such file ({})' - mode = lib.get_constant('GMT_MODULE_CMD') + msg = "gmtinfo [ERROR]: Error for input file: No such file ({})" + mode = lib.get_constant("GMT_MODULE_CMD") with lib.log_to_file() as logfile: assert os.path.exists(logfile) # Make a bogus module call that will fail - status = lib._libgmt.GMT_Call_Module(lib.current_session, - 'info'.encode(), mode, - data_file.encode()) + status = lib._libgmt.GMT_Call_Module( + lib.current_session, "info".encode(), mode, data_file.encode() + ) assert status != 0 # Check the file content with open(logfile) as flog: @@ -180,42 +191,43 @@ def logged_call_module(lib, data_file): def test_errors_sent_to_log_file(): "Make sure error messages are recorded in the log file." with LibGMT() as lib: - logged_call_module(lib, 'not-a-valid-data-file.bla') + logged_call_module(lib, "not-a-valid-data-file.bla") def test_set_log_file_twice(): "Make sure setting a log file twice in a session works" with LibGMT() as lib: - logged_call_module(lib, 'not-a-valid-data-file.bla') - logged_call_module(lib, 'another-invalid-data-file.bla') + logged_call_module(lib, "not-a-valid-data-file.bla") + logged_call_module(lib, "another-invalid-data-file.bla") def test_call_module(): "Run a command to see if call_module works" - data_fname = os.path.join(TEST_DATA_DIR, 'points.txt') - out_fname = 'test_call_module.txt' + data_fname = os.path.join(TEST_DATA_DIR, "points.txt") + out_fname = "test_call_module.txt" with LibGMT() as lib: with GMTTempFile() as out_fname: - lib.call_module('info', '{} -C ->{}'.format(data_fname, - out_fname.name)) + lib.call_module("info", "{} -C ->{}".format(data_fname, out_fname.name)) assert os.path.exists(out_fname.name) output = out_fname.read().strip() - assert output == '11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338' + assert output == "11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338" def test_call_module_error_message(): "Check that the exception has the error message from call_module" - data_file = 'bogus-data.bla' - true_msg = '\n'.join([ - "Command 'info' failed:", - "---------- Error log ----------", - 'gmtinfo [ERROR]: Error for input file: No such file (bogus-data.bla)', - "-------------------------------", - ]) + data_file = "bogus-data.bla" + true_msg = "\n".join( + [ + "Command 'info' failed:", + "---------- Error log ----------", + "gmtinfo [ERROR]: Error for input file: No such file (bogus-data.bla)", + "-------------------------------", + ] + ) with LibGMT() as lib: # Make a bogus module call that will fail try: - lib.call_module('info', data_file) + lib.call_module("info", data_file) except GMTCLibError as error: assert str(error) == true_msg else: @@ -226,7 +238,7 @@ def test_call_module_invalid_name(): "Fails when given bad input" with LibGMT() as lib: with pytest.raises(GMTCLibError): - lib.call_module('meh', '') + lib.call_module("meh", "") def test_method_no_session(): @@ -234,7 +246,7 @@ def test_method_no_session(): # Create an instance of LibGMT without "with" so no session is created. lib = LibGMT() with pytest.raises(GMTCLibNoSessionError): - lib.call_module('gmtdefaults', '') + lib.call_module("gmtdefaults", "") with pytest.raises(GMTCLibNoSessionError): lib.current_session # pylint: disable=pointless-statement @@ -250,14 +262,15 @@ def test_parse_constant_single(): def test_parse_constant_composite(): "Parsing a composite constant argument (separated by |) correctly." lib = LibGMT() - test_cases = ((family, via) - for family in lib.data_families - for via in lib.data_vias) + test_cases = ( + (family, via) for family in lib.data_families for via in lib.data_vias + ) for family, via in test_cases: - composite = '|'.join([family, via]) + composite = "|".join([family, via]) expected = lib.get_constant(family) + lib.get_constant(via) - parsed = lib._parse_constant(composite, valid=lib.data_families, - valid_modifiers=lib.data_vias) + parsed = lib._parse_constant( + composite, valid=lib.data_families, valid_modifiers=lib.data_vias + ) assert parsed == expected @@ -265,25 +278,32 @@ def test_parse_constant_fails(): "Check if the function fails when given bad input" lib = LibGMT() test_cases = [ - 'SOME_random_STRING', - 'GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR', - 'GMT_IS_DATASET|NOT_A_PROPER_VIA', - 'NOT_A_PROPER_FAMILY|GMT_VIA_MATRIX', - 'NOT_A_PROPER_FAMILY|ALSO_INVALID', + "SOME_random_STRING", + "GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR", + "GMT_IS_DATASET|NOT_A_PROPER_VIA", + "NOT_A_PROPER_FAMILY|GMT_VIA_MATRIX", + "NOT_A_PROPER_FAMILY|ALSO_INVALID", ] for test_case in test_cases: with pytest.raises(GMTInvalidInput): - lib._parse_constant(test_case, valid=lib.data_families, - valid_modifiers=lib.data_vias) + lib._parse_constant( + test_case, valid=lib.data_families, valid_modifiers=lib.data_vias + ) # Should also fail if not given valid modifiers but is using them anyway. # This should work... - lib._parse_constant('GMT_IS_DATASET|GMT_VIA_MATRIX', - valid=lib.data_families, valid_modifiers=lib.data_vias) + lib._parse_constant( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + valid=lib.data_families, + valid_modifiers=lib.data_vias, + ) # But this shouldn't. with pytest.raises(GMTInvalidInput): - lib._parse_constant('GMT_IS_DATASET|GMT_VIA_MATRIX', - valid=lib.data_families, valid_modifiers=None) + lib._parse_constant( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + valid=lib.data_families, + valid_modifiers=None, + ) def test_create_data_dataset(): @@ -291,16 +311,16 @@ def test_create_data_dataset(): with LibGMT() as lib: # Dataset from vectors data_vector = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_VECTOR', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[10, 20, 1, 0], # columns, rows, layers, dtype ) # Dataset from matrices data_matrix = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_MATRIX', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_MATRIX", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[10, 20, 1, 0], ) assert data_vector != data_matrix @@ -311,9 +331,9 @@ def test_create_data_grid_dim(): with LibGMT() as lib: # Grids from matrices using dim lib.create_data( - family='GMT_IS_GRID|GMT_VIA_MATRIX', - geometry='GMT_IS_SURFACE', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", dim=[10, 20, 1, 0], ) @@ -323,9 +343,9 @@ def test_create_data_grid_range(): with LibGMT() as lib: # Grids from matrices using range and int lib.create_data( - family='GMT_IS_GRID|GMT_VIA_MATRIX', - geometry='GMT_IS_SURFACE', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", ranges=[150., 250., -20., 20.], inc=[0.1, 0.2], ) @@ -337,9 +357,9 @@ def test_create_data_fails(): with pytest.raises(GMTInvalidInput): with LibGMT() as lib: lib.create_data( - family='GMT_IS_DATASET', - geometry='GMT_IS_SURFACE', - mode='Not_a_valid_mode', + family="GMT_IS_DATASET", + geometry="GMT_IS_SURFACE", + mode="Not_a_valid_mode", dim=[0, 0, 1, 0], ranges=[150., 250., -20., 20.], inc=[0.1, 0.2], @@ -348,9 +368,9 @@ def test_create_data_fails(): with pytest.raises(GMTInvalidInput): with LibGMT() as lib: lib.create_data( - family='GMT_IS_GRID', - geometry='Not_a_valid_geometry', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_GRID", + geometry="Not_a_valid_geometry", + mode="GMT_CONTAINER_ONLY", dim=[0, 0, 1, 0], ranges=[150., 250., -20., 20.], inc=[0.1, 0.2], @@ -359,21 +379,24 @@ def test_create_data_fails(): # If the data pointer returned is None (NULL pointer) with pytest.raises(GMTCLibError): with LibGMT() as lib: - with mock(lib, 'GMT_Create_Data', returns=None): - lib.create_data(family='GMT_IS_DATASET', - geometry='GMT_IS_SURFACE', - mode='GMT_CONTAINER_ONLY', dim=[11, 10, 2, 0]) + with mock(lib, "GMT_Create_Data", returns=None): + lib.create_data( + family="GMT_IS_DATASET", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", + dim=[11, 10, 2, 0], + ) def test_put_vector(): "Check that assigning a numpy array to a dataset works" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() for dtype in dtypes: with LibGMT() as lib: dataset = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_VECTOR', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[3, 5, 1, 0], # columns, rows, layers, dtype ) x = np.array([1, 2, 3, 4, 5], dtype=dtype) @@ -383,11 +406,17 @@ def test_put_vector(): lib.put_vector(dataset, column=lib.get_constant("GMT_Y"), vector=y) lib.put_vector(dataset, column=lib.get_constant("GMT_Z"), vector=z) # Turns out wesn doesn't matter for Datasets - wesn = [0]*6 + wesn = [0] * 6 # Save the data to a file to see if it's being accessed correctly with GMTTempFile() as tmp_file: - lib.write_data('GMT_IS_VECTOR', 'GMT_IS_POINT', - 'GMT_WRITE_SET', wesn, tmp_file.name, dataset) + lib.write_data( + "GMT_IS_VECTOR", + "GMT_IS_POINT", + "GMT_WRITE_SET", + wesn, + tmp_file.name, + dataset, + ) # Load the data and check that it's correct newx, newy, newz = tmp_file.loadtxt(unpack=True, dtype=dtype) npt.assert_allclose(newx, x) @@ -399,12 +428,12 @@ def test_put_vector_invalid_dtype(): "Check that it fails with an exception for invalid data types" with LibGMT() as lib: dataset = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_VECTOR', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[2, 3, 1, 0], # columns, rows, layers, dtype ) - data = np.array([37, 12, 556], dtype='complex128') + data = np.array([37, 12, 556], dtype="complex128") with pytest.raises(GMTInvalidInput): lib.put_vector(dataset, column=1, vector=data) @@ -413,12 +442,12 @@ def test_put_vector_wrong_column(): "Check that it fails with an exception when giving an invalid column" with LibGMT() as lib: dataset = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_VECTOR', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[1, 3, 1, 0], # columns, rows, layers, dtype ) - data = np.array([37, 12, 556], dtype='float32') + data = np.array([37, 12, 556], dtype="float32") with pytest.raises(GMTCLibError): lib.put_vector(dataset, column=1, vector=data) @@ -427,36 +456,42 @@ def test_put_vector_2d_fails(): "Check that it fails with an exception for multidimensional arrays" with LibGMT() as lib: dataset = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_VECTOR', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[1, 6, 1, 0], # columns, rows, layers, dtype ) - data = np.array([[37, 12, 556], [37, 12, 556]], dtype='int32') + data = np.array([[37, 12, 556], [37, 12, 556]], dtype="int32") with pytest.raises(GMTInvalidInput): lib.put_vector(dataset, column=0, vector=data) def test_put_matrix(): "Check that assigning a numpy 2d array to a dataset works" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (3, 4) for dtype in dtypes: with LibGMT() as lib: dataset = lib.create_data( - family='GMT_IS_DATASET|GMT_VIA_MATRIX', - geometry='GMT_IS_POINT', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_DATASET|GMT_VIA_MATRIX", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype ) - data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) lib.put_matrix(dataset, matrix=data) # wesn doesn't matter for Datasets - wesn = [0]*6 + wesn = [0] * 6 # Save the data to a file to see if it's being accessed correctly with GMTTempFile() as tmp_file: - lib.write_data('GMT_IS_MATRIX', 'GMT_IS_POINT', - 'GMT_WRITE_SET', wesn, tmp_file.name, dataset) + lib.write_data( + "GMT_IS_MATRIX", + "GMT_IS_POINT", + "GMT_WRITE_SET", + wesn, + tmp_file.name, + dataset, + ) # Load the data and check that it's correct newdata = tmp_file.loadtxt(dtype=dtype) npt.assert_allclose(newdata, data) @@ -468,34 +503,39 @@ def test_put_matrix_fails(): # checks on input arguments. Mock the C API function just to make sure it # works. with LibGMT() as lib: - with mock(lib, 'GMT_Put_Matrix', returns=1): + with mock(lib, "GMT_Put_Matrix", returns=1): with pytest.raises(GMTCLibError): lib.put_matrix(dataset=None, matrix=np.empty((10, 2)), pad=0) def test_put_matrix_grid(): "Check that assigning a numpy 2d array to a grid works" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() wesn = [10, 15, 30, 40, 0, 0] inc = [1, 1] - shape = ((wesn[3] - wesn[2])//inc[1] + 1, (wesn[1] - wesn[0])//inc[0] + 1) + shape = ((wesn[3] - wesn[2]) // inc[1] + 1, (wesn[1] - wesn[0]) // inc[0] + 1) for dtype in dtypes: with LibGMT() as lib: grid = lib.create_data( - family='GMT_IS_GRID|GMT_VIA_MATRIX', - geometry='GMT_IS_SURFACE', - mode='GMT_CONTAINER_ONLY', + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", ranges=wesn[:4], inc=inc, - registration='GMT_GRID_NODE_REG', + registration="GMT_GRID_NODE_REG", ) - data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) lib.put_matrix(grid, matrix=data) # Save the data to a file to see if it's being accessed correctly with GMTTempFile() as tmp_file: - lib.write_data('GMT_IS_MATRIX', 'GMT_IS_SURFACE', - 'GMT_CONTAINER_AND_DATA', - wesn, tmp_file.name, grid) + lib.write_data( + "GMT_IS_MATRIX", + "GMT_IS_SURFACE", + "GMT_CONTAINER_AND_DATA", + wesn, + tmp_file.name, + grid, + ) # Load the data and check that it's correct newdata = tmp_file.loadtxt(dtype=dtype) npt.assert_allclose(newdata, data) @@ -503,30 +543,30 @@ def test_put_matrix_grid(): def test_virtual_file(): "Test passing in data via a virtual file with a Dataset" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (5, 3) for dtype in dtypes: with LibGMT() as lib: - family = 'GMT_IS_DATASET|GMT_VIA_MATRIX' - geometry = 'GMT_IS_POINT' + family = "GMT_IS_DATASET|GMT_VIA_MATRIX" + geometry = "GMT_IS_POINT" dataset = lib.create_data( family=family, geometry=geometry, - mode='GMT_CONTAINER_ONLY', + mode="GMT_CONTAINER_ONLY", dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype ) - data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) lib.put_matrix(dataset, matrix=data) # Add the dataset to a virtual file and pass it along to gmt info - vfargs = (family, geometry, 'GMT_IN', dataset) + vfargs = (family, geometry, "GMT_IN", dataset) with lib.open_virtual_file(*vfargs) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(col.min(), col.max()) - for col in data.T]) - expected = ': N = {}\t{}\n'.format(shape[0], bounds) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(shape[0], bounds) assert output == expected @@ -535,12 +575,12 @@ def test_virtual_file_fails(): Check that opening and closing virtual files raises an exception for non-zero return codes """ - vfargs = ('GMT_IS_DATASET|GMT_VIA_MATRIX', 'GMT_IS_POINT', 'GMT_IN', None) + vfargs = ("GMT_IS_DATASET|GMT_VIA_MATRIX", "GMT_IS_POINT", "GMT_IN", None) # Mock Open_VirtualFile to test the status check when entering the context. # If the exception is raised, the code won't get to the closing of the # virtual file. - with LibGMT() as lib, mock(lib, 'GMT_Open_VirtualFile', returns=1): + with LibGMT() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): with pytest.raises(GMTCLibError): with lib.open_virtual_file(*vfargs): print("Should not get to this code") @@ -548,8 +588,9 @@ def test_virtual_file_fails(): # Test the status check when closing the virtual file # Mock the opening to return 0 (success) so that we don't open a file that # we won't close later. - with LibGMT() as lib, mock(lib, 'GMT_Open_VirtualFile', returns=0), \ - mock(lib, 'GMT_Close_VirtualFile', returns=1): + with LibGMT() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( + lib, "GMT_Close_VirtualFile", returns=1 + ): with pytest.raises(GMTCLibError): with lib.open_virtual_file(*vfargs): print("Shouldn't get to this code either") @@ -558,9 +599,12 @@ def test_virtual_file_fails(): def test_virtual_file_bad_direction(): "Test passing an invalid direction argument" with LibGMT() as lib: - vfargs = ('GMT_IS_DATASET|GMT_VIA_MATRIX', 'GMT_IS_POINT', - 'GMT_IS_GRID', # The invalid direction argument - 0) + vfargs = ( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + "GMT_IS_POINT", + "GMT_IS_GRID", # The invalid direction argument + 0, + ) with pytest.raises(GMTInvalidInput): with lib.open_virtual_file(*vfargs): print("This should have failed") @@ -568,39 +612,39 @@ def test_virtual_file_bad_direction(): def test_vectors_to_vfile(): "Test the automation for transforming vectors to virtual file dataset" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() size = 10 for dtype in dtypes: x = np.arange(size, dtype=dtype) - y = np.arange(size, size*2, 1, dtype=dtype) - z = np.arange(size*2, size*3, 1, dtype=dtype) + y = np.arange(size, size * 2, 1, dtype=dtype) + z = np.arange(size * 2, size * 3, 1, dtype=dtype) with LibGMT() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(i.min(), i.max()) - for i in (x, y, z)]) - expected = ': N = {}\t{}\n'.format(size, bounds) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(i.min(), i.max()) for i in (x, y, z)] + ) + expected = ": N = {}\t{}\n".format(size, bounds) assert output == expected def test_vectors_to_vfile_transpose(): "Test transforming matrix columns to virtual file dataset" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (7, 5) for dtype in dtypes: - data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) with LibGMT() as lib: with lib.vectors_to_vfile(*data.T) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} -C ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['{:.0f}\t{:.0f}'.format(col.min(), col.max()) - for col in data.T]) - expected = '{}\n'.format(bounds) + bounds = "\t".join( + ["{:.0f}\t{:.0f}".format(col.min(), col.max()) for col in data.T] + ) + expected = "{}\n".format(bounds) assert output == expected @@ -616,62 +660,67 @@ def test_vectors_to_vfile_diff_size(): def test_matrix_to_vfile(): "Test transforming a matrix to virtual file dataset" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (7, 5) for dtype in dtypes: - data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) with LibGMT() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(col.min(), col.max()) - for col in data.T]) - expected = ': N = {}\t{}\n'.format(shape[0], bounds) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(shape[0], bounds) assert output == expected def test_matrix_to_vfile_slice(): "Test transforming a slice of a larger array to virtual file dataset" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (10, 6) for dtype in dtypes: - full_data = np.arange(shape[0]*shape[1], dtype=dtype).reshape(shape) + full_data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) rows = 5 cols = 3 data = full_data[:rows, :cols] with LibGMT() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(col.min(), col.max()) - for col in data.T]) - expected = ': N = {}\t{}\n'.format(rows, bounds) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(rows, bounds) assert output == expected def test_vectors_to_vfile_pandas(): "Pass vectors to a dataset using pandas Series" - dtypes = 'float32 float64 int32 int64 uint32 uint64'.split() + dtypes = "float32 float64 int32 int64 uint32 uint64".split() size = 13 for dtype in dtypes: data = pd.DataFrame( - data=dict(x=np.arange(size, dtype=dtype), - y=np.arange(size, size*2, 1, dtype=dtype), - z=np.arange(size*2, size*3, 1, dtype=dtype)) + data=dict( + x=np.arange(size, dtype=dtype), + y=np.arange(size, size * 2, 1, dtype=dtype), + z=np.arange(size * 2, size * 3, 1, dtype=dtype), + ) ) with LibGMT() as lib: with lib.vectors_to_vfile(data.x, data.y, data.z) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, - outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(i.min(), i.max()) - for i in (data.x, data.y, data.z)]) - expected = ': N = {}\t{}\n'.format(size, bounds) + bounds = "\t".join( + [ + "<{:.0f}/{:.0f}>".format(i.min(), i.max()) + for i in (data.x, data.y, data.z) + ] + ) + expected = ": N = {}\t{}\n".format(size, bounds) assert output == expected @@ -679,16 +728,17 @@ def test_vectors_to_vfile_arraylike(): "Pass array-like vectors to a dataset" size = 13 x = list(range(0, size, 1)) - y = tuple(range(size, size*2, 1)) - z = range(size*2, size*3, 1) + y = tuple(range(size, size * 2, 1)) + z = range(size * 2, size * 3, 1) with LibGMT() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: - lib.call_module('info', '{} ->{}'.format(vfile, outfile.name)) + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) - bounds = '\t'.join(['<{:.0f}/{:.0f}>'.format(min(i), max(i)) - for i in (x, y, z)]) - expected = ': N = {}\t{}\n'.format(size, bounds) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(min(i), max(i)) for i in (x, y, z)] + ) + expected = ": N = {}\t{}\n".format(size, bounds) assert output == expected @@ -706,22 +756,22 @@ def test_extract_region_two_figures(): # getting from the current figure, not the last figure. fig1 = Figure() region1 = np.array([0, 10, -20, -10]) - fig1.coast(region=region1, projection="M6i", frame=True, land='black') + fig1.coast(region=region1, projection="M6i", frame=True, land="black") fig2 = Figure() - fig2.basemap(region='US.HI+r5', projection="M6i", frame=True) + fig2.basemap(region="US.HI+r5", projection="M6i", frame=True) # Activate the first figure and extract the region from it # Use in a different session to avoid any memory problems. with LibGMT() as lib: - lib.call_module('figure', '{} -'.format(fig1._name)) + lib.call_module("figure", "{} -".format(fig1._name)) with LibGMT() as lib: wesn1 = lib.extract_region() npt.assert_allclose(wesn1, region1) # Now try it with the second one with LibGMT() as lib: - lib.call_module('figure', '{} -'.format(fig2._name)) + lib.call_module("figure", "{} -".format(fig2._name)) with LibGMT() as lib: wesn2 = lib.extract_region() npt.assert_allclose(wesn2, np.array([-165., -150., 15., 25.])) @@ -734,30 +784,36 @@ def test_write_data_fails(): # output=='', GMT will just write to stdout and spaces are valid file # names. Use a mock instead just to exercise this part of the code. with LibGMT() as lib: - with mock(lib, 'GMT_Write_Data', returns=1): + with mock(lib, "GMT_Write_Data", returns=1): with pytest.raises(GMTCLibError): - lib.write_data('GMT_IS_VECTOR', 'GMT_IS_POINT', - 'GMT_WRITE_SET', [1]*6, 'some-file-name', None) + lib.write_data( + "GMT_IS_VECTOR", + "GMT_IS_POINT", + "GMT_WRITE_SET", + [1] * 6, + "some-file-name", + None, + ) def test_dataarray_to_matrix_dims_fails(): "Check that it fails for > 2 dims" # Make a 3D regular grid - data = np.ones((10, 12, 11), dtype='float32') + data = np.ones((10, 12, 11), dtype="float32") x = np.arange(11) y = np.arange(12) z = np.arange(10) - grid = xr.DataArray(data, coords=[('z', z), ('y', y), ('x', x)]) + grid = xr.DataArray(data, coords=[("z", z), ("y", y), ("x", x)]) with pytest.raises(GMTInvalidInput): dataarray_to_matrix(grid) def test_dataarray_to_matrix_inc_fails(): "Check that it fails for variable increments" - data = np.ones((4, 5), dtype='float64') + data = np.ones((4, 5), dtype="float64") x = np.linspace(0, 1, 5) y = np.logspace(2, 3, 4) - grid = xr.DataArray(data, coords=[('y', y), ('x', x)]) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) with pytest.raises(GMTInvalidInput): dataarray_to_matrix(grid) @@ -765,16 +821,16 @@ def test_dataarray_to_matrix_inc_fails(): def test_get_default(): "Make sure get_default works without crashing and gives reasonable results" with LibGMT() as lib: - assert lib.get_default('API_GRID_LAYOUT') in ['rows', 'columns'] - assert int(lib.get_default('API_CORES')) >= 1 - assert Version(lib.get_default('API_VERSION')) >= Version('6.0.0') + assert lib.get_default("API_GRID_LAYOUT") in ["rows", "columns"] + assert int(lib.get_default("API_CORES")) >= 1 + assert Version(lib.get_default("API_VERSION")) >= Version("6.0.0") def test_get_default_fails(): "Make sure get_default raises an exception for invalid names" with LibGMT() as lib: with pytest.raises(GMTCLibError): - lib.get_default('NOT_A_VALID_NAME') + lib.get_default("NOT_A_VALID_NAME") def test_info_dict(): @@ -791,12 +847,12 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument return 0 with LibGMT() as lib: - with mock(lib, 'GMT_Get_Default', mock_func=mock_defaults): + with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): info = lib.info # Check for an empty dictionary assert info for key in info: - assert info[key] == 'bla' + assert info[key] == "bla" def test_fails_for_wrong_version(): @@ -805,17 +861,17 @@ def test_fails_for_wrong_version(): # Mock GMT_Get_Default to return an old version def mock_defaults(api, name, value): # pylint: disable=unused-argument "Return an old version" - if name == b'API_VERSION': + if name == b"API_VERSION": value.value = b"5.4.3" else: value.value = b"bla" return 0 lib = LibGMT() - with mock(lib, 'GMT_Get_Default', mock_func=mock_defaults): + with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): with pytest.raises(GMTVersionError): with lib: - assert lib.info['version'] != '5.4.3' + assert lib.info["version"] != "5.4.3" # Make sure the session is closed when the exception is raised. with pytest.raises(GMTCLibNoSessionError): assert lib.current_session diff --git a/gmt/tests/test_coast.py b/gmt/tests/test_coast.py index 5341ee142a0..a1bd8addc68 100644 --- a/gmt/tests/test_coast.py +++ b/gmt/tests/test_coast.py @@ -10,8 +10,18 @@ def test_coast(): "Simple plot from the GMT docs" fig = Figure() - fig.coast(R='-30/30/-40/40', J='m0.1i', B=5, I='1/1p,blue', N='1/0.25p,-', - W='0.25p,white', G='green', S='blue', D='c', A=10000) + fig.coast( + R="-30/30/-40/40", + J="m0.1i", + B=5, + I="1/1p,blue", + N="1/0.25p,-", + W="0.25p,white", + G="green", + S="blue", + D="c", + A=10000, + ) return fig @@ -19,7 +29,7 @@ def test_coast(): def test_coast_iceland(): "Test passing in R as a list" fig = Figure() - fig.coast(R=[-30, -10, 60, 65], J='m1c', B=True, G='p28+r100') + fig.coast(R=[-30, -10, 60, 65], J="m1c", B=True, G="p28+r100") return fig @@ -27,10 +37,18 @@ def test_coast_iceland(): def test_coast_aliases(): "Test that all aliases work" fig = Figure() - fig.coast(region='-30/30/-40/40', projection='m0.1i', frame='afg', - rivers='1/1p,black', borders='1/0.5p,-', - shorelines='0.25p,white', land='moccasin', water='skyblue', - resolution='i', area_thresh=1000) + fig.coast( + region="-30/30/-40/40", + projection="m0.1i", + frame="afg", + rivers="1/1p,black", + borders="1/0.5p,-", + shorelines="0.25p,white", + land="moccasin", + water="skyblue", + resolution="i", + area_thresh=1000, + ) return fig @@ -38,6 +56,12 @@ def test_coast_aliases(): def test_coast_world_mercator(): "Test passing generating a global Mercator map with coastlines" fig = Figure() - fig.coast(region=[-180, 180, -80, 80], projection='M10i', frame='af', - land='#aaaaaa', resolution='l', water='white') + fig.coast( + region=[-180, 180, -80, 80], + projection="M10i", + frame="af", + land="#aaaaaa", + resolution="l", + water="white", + ) return fig diff --git a/gmt/tests/test_datasets.py b/gmt/tests/test_datasets.py index 9189c1f1f75..05db4c5f52a 100644 --- a/gmt/tests/test_datasets.py +++ b/gmt/tests/test_datasets.py @@ -14,17 +14,17 @@ def test_japan_quakes(): data = load_japan_quakes() assert data.shape == (115, 7) summary = data.describe() - assert summary.loc['min', 'year'] == 1987 - assert summary.loc['max', 'year'] == 1988 - assert summary.loc['min', 'month'] == 1 - assert summary.loc['max', 'month'] == 12 - assert summary.loc['min', 'day'] == 1 - assert summary.loc['max', 'day'] == 31 + assert summary.loc["min", "year"] == 1987 + assert summary.loc["max", "year"] == 1988 + assert summary.loc["min", "month"] == 1 + assert summary.loc["max", "month"] == 12 + assert summary.loc["min", "day"] == 1 + assert summary.loc["max", "day"] == 31 def test_earth_relief_fails(): "Make sure earth relief fails for invalid resolutions" - resolutions = '1m 1d bla 60d 01s 03s 001m 03'.split() + resolutions = "1m 1d bla 60d 01s 03s 001m 03".split() resolutions.append(60) for resolution in resolutions: with pytest.raises(GMTInvalidInput): @@ -34,7 +34,7 @@ def test_earth_relief_fails(): # Only test 60m and 30m to avoid downloading large datasets in CI def test_earth_relief_60(): "Test some properties of the earth relief 60m data" - data = load_earth_relief(resolution='60m') + data = load_earth_relief(resolution="60m") assert data.shape == (181, 361) npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) @@ -44,7 +44,7 @@ def test_earth_relief_60(): def test_earth_relief_30(): "Test some properties of the earth relief 30m data" - data = load_earth_relief(resolution='30m') + data = load_earth_relief(resolution="30m") assert data.shape == (361, 721) npt.assert_allclose(data.lat, np.arange(-90, 90.5, 0.5)) npt.assert_allclose(data.lon, np.arange(-180, 180.5, 0.5)) diff --git a/gmt/tests/test_figure.py b/gmt/tests/test_figure.py index ca26b457da7..a1d5d2371e9 100644 --- a/gmt/tests/test_figure.py +++ b/gmt/tests/test_figure.py @@ -27,7 +27,7 @@ def test_figure_region_multiple(): fig1.basemap(region=region1, projection="X1id/1id", frame=True) fig2 = Figure() - fig2.basemap(region='g', projection="X3id/3id", frame=True) + fig2.basemap(region="g", projection="X3id/3id", frame=True) npt.assert_allclose(fig1.region, np.array(region1)) npt.assert_allclose(fig2.region, np.array([0.0, 360.0, -90.0, 90.0])) @@ -36,23 +36,22 @@ def test_figure_region_multiple(): def test_figure_region_country_codes(): "Extract the plot region for the figure using country codes" fig = Figure() - fig.basemap(region='JP', projection="M3i", frame=True) - npt.assert_allclose(fig.region, - np.array([122.938515, 145.820877, 20.528774, - 45.523136])) + fig.basemap(region="JP", projection="M3i", frame=True) + npt.assert_allclose( + fig.region, np.array([122.938515, 145.820877, 20.528774, 45.523136]) + ) fig = Figure() - fig.basemap(region='g', projection="X3id/3id", frame=True) + fig.basemap(region="g", projection="X3id/3id", frame=True) npt.assert_allclose(fig.region, np.array([0.0, 360.0, -90.0, 90.0])) def test_figure_savefig_exists(): "Make sure the saved figure has the right name" fig = Figure() - fig.basemap(region='10/70/-300/800', J='X3i/5i', B='af', - D='30/35/-200/500', F=True) - prefix = 'test_figure_savefig_exists' - for fmt in 'png pdf jpg bmp eps tif'.split(): - fname = '.'.join([prefix, fmt]) + fig.basemap(region="10/70/-300/800", J="X3i/5i", B="af", D="30/35/-200/500", F=True) + prefix = "test_figure_savefig_exists" + for fmt in "png pdf jpg bmp eps tif".split(): + fname = ".".join([prefix, fmt]) fig.savefig(fname) assert os.path.exists(fname) os.remove(fname) @@ -61,15 +60,14 @@ def test_figure_savefig_exists(): def test_figure_savefig_transparent(): "Check if fails when transparency is not supported" fig = Figure() - fig.basemap(region='10/70/-300/800', J='X3i/5i', B='af', - D='30/35/-200/500', F=True) - prefix = 'test_figure_savefig_transparent' - for fmt in 'pdf jpg bmp eps tif'.split(): - fname = '.'.join([prefix, fmt]) + fig.basemap(region="10/70/-300/800", J="X3i/5i", B="af", D="30/35/-200/500", F=True) + prefix = "test_figure_savefig_transparent" + for fmt in "pdf jpg bmp eps tif".split(): + fname = ".".join([prefix, fmt]) with pytest.raises(GMTInvalidInput): fig.savefig(fname, transparent=True) # png should not raise an error - fname = '.'.join([prefix, 'png']) + fname = ".".join([prefix, "png"]) fig.savefig(fname, transparent=True) assert os.path.exists(fname) os.remove(fname) @@ -86,38 +84,34 @@ def mock_psconvert(*args, **kwargs): # pylint: disable=unused-argument fig = Figure() fig.psconvert = mock_psconvert - prefix = 'test_figure_savefig' + prefix = "test_figure_savefig" - fname = '.'.join([prefix, 'png']) + fname = ".".join([prefix, "png"]) fig.savefig(fname) - assert kwargs_saved[-1] == dict(prefix=prefix, fmt='g', crop=True, Qt=2, - Qg=2) + assert kwargs_saved[-1] == dict(prefix=prefix, fmt="g", crop=True, Qt=2, Qg=2) - fname = '.'.join([prefix, 'pdf']) + fname = ".".join([prefix, "pdf"]) fig.savefig(fname) - assert kwargs_saved[-1] == dict(prefix=prefix, fmt='f', crop=True, Qt=2, - Qg=2) + assert kwargs_saved[-1] == dict(prefix=prefix, fmt="f", crop=True, Qt=2, Qg=2) - fname = '.'.join([prefix, 'png']) + fname = ".".join([prefix, "png"]) fig.savefig(fname, transparent=True) - assert kwargs_saved[-1] == dict(prefix=prefix, fmt='G', crop=True, Qt=2, - Qg=2) + assert kwargs_saved[-1] == dict(prefix=prefix, fmt="G", crop=True, Qt=2, Qg=2) - fname = '.'.join([prefix, 'eps']) + fname = ".".join([prefix, "eps"]) fig.savefig(fname) - assert kwargs_saved[-1] == dict(prefix=prefix, fmt='e', crop=True, Qt=2, - Qg=2) + assert kwargs_saved[-1] == dict(prefix=prefix, fmt="e", crop=True, Qt=2, Qg=2) - fname = '.'.join([prefix, 'kml']) + fname = ".".join([prefix, "kml"]) fig.savefig(fname) - assert kwargs_saved[-1] == dict(prefix=prefix, fmt='g', crop=True, Qt=2, - Qg=2, W='+k') + assert kwargs_saved[-1] == dict( + prefix=prefix, fmt="g", crop=True, Qt=2, Qg=2, W="+k" + ) def test_figure_show(): "Test that show creates the correct file name and deletes the temp dir" fig = Figure() - fig.basemap(R='10/70/-300/800', J='X3i/5i', B='af', D='30/35/-200/500', - F=True) + fig.basemap(R="10/70/-300/800", J="X3i/5i", B="af", D="30/35/-200/500", F=True) img = fig.show(width=800) assert img.width == 800 diff --git a/gmt/tests/test_grdimage.py b/gmt/tests/test_grdimage.py index dd42d1bb7a6..7c5417850c5 100644 --- a/gmt/tests/test_grdimage.py +++ b/gmt/tests/test_grdimage.py @@ -13,8 +13,13 @@ def test_grdimage_file(): "Plot an image using file input" fig = Figure() - fig.grdimage("@earth_relief_60m", cmap='ocean', region='-180/180/-70/70', - projection='W0/10i', shading=True) + fig.grdimage( + "@earth_relief_60m", + cmap="ocean", + region="-180/180/-70/70", + projection="W0/10i", + shading=True, + ) return fig diff --git a/gmt/tests/test_helpers.py b/gmt/tests/test_helpers.py index 92e44e5b4aa..febec2738e4 100644 --- a/gmt/tests/test_helpers.py +++ b/gmt/tests/test_helpers.py @@ -12,7 +12,7 @@ def test_unique_name(): "Make sure the names start with gmt-python- and are really unique" names = [unique_name() for i in range(100)] - assert all([name.startswith('gmt-python-') for name in names]) + assert all([name.startswith("gmt-python-") for name in names]) assert len(names) == len(set(names)) @@ -31,7 +31,7 @@ def my_module(**kwargs): return kwargs # The module should return the exact same arguments it was given - args = dict(P=True, A=False, R='1/2/3/4') + args = dict(P=True, A=False, R="1/2/3/4") assert my_module(**args) == args @@ -54,23 +54,23 @@ def test_gmttempfile_unique(): def test_gmttempfile_prefix_suffix(): "Make sure the prefix and suffix of temporary files are user specifiable" with GMTTempFile() as tmpfile: - assert os.path.basename(tmpfile.name).startswith('gmt-python-') - assert os.path.basename(tmpfile.name).endswith('.txt') + assert os.path.basename(tmpfile.name).startswith("gmt-python-") + assert os.path.basename(tmpfile.name).endswith(".txt") with GMTTempFile(prefix="user-prefix-") as tmpfile: - assert os.path.basename(tmpfile.name).startswith('user-prefix-') - assert os.path.basename(tmpfile.name).endswith('.txt') - with GMTTempFile(suffix='.log') as tmpfile: - assert os.path.basename(tmpfile.name).startswith('gmt-python-') - assert os.path.basename(tmpfile.name).endswith('.log') + assert os.path.basename(tmpfile.name).startswith("user-prefix-") + assert os.path.basename(tmpfile.name).endswith(".txt") + with GMTTempFile(suffix=".log") as tmpfile: + assert os.path.basename(tmpfile.name).startswith("gmt-python-") + assert os.path.basename(tmpfile.name).endswith(".log") with GMTTempFile(prefix="user-prefix-", suffix=".log") as tmpfile: - assert os.path.basename(tmpfile.name).startswith('user-prefix-') - assert os.path.basename(tmpfile.name).endswith('.log') + assert os.path.basename(tmpfile.name).startswith("user-prefix-") + assert os.path.basename(tmpfile.name).endswith(".log") def test_gmttempfile_read(): "Make sure GMTTempFile.read() works" with GMTTempFile() as tmpfile: with open(tmpfile.name, "w") as ftmp: - ftmp.write('in.dat: N = 2\t<1/3>\t<2/4>\n') - assert tmpfile.read() == 'in.dat: N = 2 <1/3> <2/4>\n' - assert tmpfile.read(keep_tabs=True) == 'in.dat: N = 2\t<1/3>\t<2/4>\n' + ftmp.write("in.dat: N = 2\t<1/3>\t<2/4>\n") + assert tmpfile.read() == "in.dat: N = 2 <1/3> <2/4>\n" + assert tmpfile.read(keep_tabs=True) == "in.dat: N = 2\t<1/3>\t<2/4>\n" diff --git a/gmt/tests/test_info.py b/gmt/tests/test_info.py index 2b4e81e6eab..8bbd0640b2a 100644 --- a/gmt/tests/test_info.py +++ b/gmt/tests/test_info.py @@ -10,42 +10,41 @@ from ..exceptions import GMTInvalidInput from ..datasets import load_earth_relief -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -POINTS_DATA = os.path.join(TEST_DATA_DIR, 'points.txt') +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") def test_info(): "Make sure info works" output = info(fname=POINTS_DATA) - expected_output = ('{}: N = 20 ' - '<11.5309/61.7074> ' - '<-2.9289/7.8648> ' - '<0.1412/0.9338>\n').format(POINTS_DATA) + expected_output = ( + "{}: N = 20 " "<11.5309/61.7074> " "<-2.9289/7.8648> " "<0.1412/0.9338>\n" + ).format(POINTS_DATA) assert output == expected_output def test_info_c(): "Make sure the C option works" output = info(fname=POINTS_DATA, C=True) - assert output == '11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338\n' + assert output == "11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338\n" def test_info_i(): "Make sure the I option works" output = info(fname=POINTS_DATA, I=0.1) - assert output == '-R11.5/61.8/-3/7.9\n' + assert output == "-R11.5/61.8/-3/7.9\n" def test_info_c_i(): "Make sure the C and I options work together" output = info(fname=POINTS_DATA, C=True, I=0.1) - assert output == '11.5 61.8 -3 7.9 0.1412 0.9338\n' + assert output == "11.5 61.8 -3 7.9 0.1412 0.9338\n" def test_info_t(): "Make sure the T option works" output = info(fname=POINTS_DATA, T=0.1) - assert output == '-T11.5/61.8/0.1\n' + assert output == "-T11.5/61.8/0.1\n" def test_info_fails(): @@ -59,13 +58,13 @@ def test_info_fails(): def test_grdinfo(): "Make sure grd info works as expected" grid = load_earth_relief() - result = grdinfo(grid, L=0, C='n') + result = grdinfo(grid, L=0, C="n") assert result.strip() == "-180 180 -90 90 -8425 5551 1 1 361 181" def test_grdinfo_file(): "Test grdinfo with file input" - result = grdinfo("@earth_relief_60m", L=0, C='n') + result = grdinfo("@earth_relief_60m", L=0, C="n") assert result.strip() == "-180 180 -90 90 -8425 5551 1 1 361 181" diff --git a/gmt/tests/test_logo.py b/gmt/tests/test_logo.py index bc8e4bdf189..d4185f115c2 100644 --- a/gmt/tests/test_logo.py +++ b/gmt/tests/test_logo.py @@ -19,9 +19,8 @@ def test_logo(): def test_logo_on_a_map(): "Plot a GMT logo in the upper right corner of a map" fig = Figure() - fig.coast(region=[-90, -70, 0, 20], projection='M6i', land='chocolate', - frame=True) - fig.logo(D='jTR+o0.1i/0.1i+w3i', F=True) + fig.coast(region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True) + fig.logo(D="jTR+o0.1i/0.1i+w3i", F=True) return fig diff --git a/gmt/tests/test_plot.py b/gmt/tests/test_plot.py index 2977449ac4d..a36cf6c0239 100644 --- a/gmt/tests/test_plot.py +++ b/gmt/tests/test_plot.py @@ -11,17 +11,17 @@ from ..exceptions import GMTInvalidInput -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -POINTS_DATA = os.path.join(TEST_DATA_DIR, 'points.txt') +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def data(): "Load the point data from the test file" return np.loadtxt(POINTS_DATA) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def region(): "The data region" return [10, 70, -5, 10] @@ -31,8 +31,15 @@ def region(): def test_plot_red_circles(data, region): "Plot the data in red circles passing in vectors" fig = Figure() - fig.plot(x=data[:, 0], y=data[:, 1], region=region, projection='X4i', - style='c0.2c', color='red', frame='afg') + fig.plot( + x=data[:, 0], + y=data[:, 1], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) return fig @@ -40,37 +47,78 @@ def test_plot_fail_no_data(data): "Plot should raise an exception if no data is given" fig = Figure() with pytest.raises(GMTInvalidInput): - fig.plot(region=region, projection='X4i', style='c0.2c', color='red', - frame='afg') + fig.plot( + region=region, projection="X4i", style="c0.2c", color="red", frame="afg" + ) with pytest.raises(GMTInvalidInput): - fig.plot(x=data[:, 0], region=region, projection='X4i', style='c0.2c', - color='red', frame='afg') + fig.plot( + x=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) with pytest.raises(GMTInvalidInput): - fig.plot(y=data[:, 0], region=region, projection='X4i', style='c0.2c', - color='red', frame='afg') + fig.plot( + y=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) # Should also fail if given too much data with pytest.raises(GMTInvalidInput): - fig.plot(x=data[:, 0], y=data[:, 1], data=data, region=region, - projection='X4i', style='c0.2c', color='red', frame='afg') + fig.plot( + x=data[:, 0], + y=data[:, 1], + data=data, + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) def test_plot_fail_size_color(data): "Should raise an exception if array sizes and color are used with matrix" fig = Figure() with pytest.raises(GMTInvalidInput): - fig.plot(data=data, region=region, projection='X4i', style='c0.2c', - color=data[:, 2], frame='afg') + fig.plot( + data=data, + region=region, + projection="X4i", + style="c0.2c", + color=data[:, 2], + frame="afg", + ) with pytest.raises(GMTInvalidInput): - fig.plot(data=data, region=region, projection='X4i', style='cc', - sizes=data[:, 2], color='red', frame='afg') + fig.plot( + data=data, + region=region, + projection="X4i", + style="cc", + sizes=data[:, 2], + color="red", + frame="afg", + ) @pytest.mark.mpl_image_compare def test_plot_projection(data): "Plot the data in green squares with a projection" fig = Figure() - fig.plot(x=data[:, 0], y=data[:, 1], region='g', projection='R270/4i', - style='s0.2c', color='green', frame='ag') + fig.plot( + x=data[:, 0], + y=data[:, 1], + region="g", + projection="R270/4i", + style="s0.2c", + color="green", + frame="ag", + ) return fig @@ -78,8 +126,16 @@ def test_plot_projection(data): def test_plot_colors(data, region): "Plot the data using z as sizes" fig = Figure() - fig.plot(x=data[:, 0], y=data[:, 1], color=data[:, 2], region=region, - projection='X3i', style='c0.5c', cmap='cubhelix', frame='af') + fig.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + region=region, + projection="X3i", + style="c0.5c", + cmap="cubhelix", + frame="af", + ) return fig @@ -87,8 +143,16 @@ def test_plot_colors(data, region): def test_plot_sizes(data, region): "Plot the data using z as sizes" fig = Figure() - fig.plot(x=data[:, 0], y=data[:, 1], sizes=0.5*data[:, 2], region=region, - projection='X4i', style='cc', color='blue', frame='af') + fig.plot( + x=data[:, 0], + y=data[:, 1], + sizes=0.5 * data[:, 2], + region=region, + projection="X4i", + style="cc", + color="blue", + frame="af", + ) return fig @@ -96,9 +160,17 @@ def test_plot_sizes(data, region): def test_plot_colors_sizes(data, region): "Plot the data using z as sizes and colors" fig = Figure() - fig.plot(x=data[:, 0], y=data[:, 1], color=data[:, 2], - sizes=0.5*data[:, 2], region=region, projection='X3i', style='cc', - cmap='copper', frame='af') + fig.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + sizes=0.5 * data[:, 2], + region=region, + projection="X3i", + style="cc", + cmap="copper", + frame="af", + ) return fig @@ -106,10 +178,15 @@ def test_plot_colors_sizes(data, region): def test_plot_colors_sizes_proj(data, region): "Plot the data using z as sizes and colors with a projection" fig = Figure() - fig.coast(region=region, projection='M10i', frame='af', water='skyblue') - fig.plot(x=data[:, 0], y=data[:, 1], color=data[:, 2], - sizes=0.5*data[:, 2], style='cc', - cmap='copper') + fig.coast(region=region, projection="M10i", frame="af", water="skyblue") + fig.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + sizes=0.5 * data[:, 2], + style="cc", + cmap="copper", + ) return fig @@ -117,8 +194,15 @@ def test_plot_colors_sizes_proj(data, region): def test_plot_matrix(data): "Plot the data passing in a matrix and specifying columns" fig = Figure() - fig.plot(data=data, region=[10, 70, -5, 10], projection='M10i', style='cc', - color='#aaaaaa', B='a', columns='0,1,2+s0.005') + fig.plot( + data=data, + region=[10, 70, -5, 10], + projection="M10i", + style="cc", + color="#aaaaaa", + B="a", + columns="0,1,2+s0.005", + ) return fig @@ -126,8 +210,14 @@ def test_plot_matrix(data): def test_plot_matrix_color(data): "Plot the data passing in a matrix and using a colormap" fig = Figure() - fig.plot(data=data, region=[10, 70, -5, 10], projection='X5i', - style='c0.5c', cmap='rainbow', B='a') + fig.plot( + data=data, + region=[10, 70, -5, 10], + projection="X5i", + style="c0.5c", + cmap="rainbow", + B="a", + ) return fig @@ -135,8 +225,15 @@ def test_plot_matrix_color(data): def test_plot_from_file(region): "Plot using the data file name instead of loaded data" fig = Figure() - fig.plot(data=POINTS_DATA, region=region, projection='X10i', style='d1c', - color='yellow', frame=True, columns=[0, 1]) + fig.plot( + data=POINTS_DATA, + region=region, + projection="X10i", + style="d1c", + color="yellow", + frame=True, + columns=[0, 1], + ) return fig @@ -148,6 +245,14 @@ def test_plot_vectors(): lon = np.sin(np.deg2rad(azimuth)) lat = np.cos(np.deg2rad(azimuth)) fig = Figure() - fig.plot(x=lon, y=lat, direction=(azimuth, lengths), region='-2/2/-2/2', - projection='X4i', style='V0.2c+e', color='black', frame='af') + fig.plot( + x=lon, + y=lat, + direction=(azimuth, lengths), + region="-2/2/-2/2", + projection="X4i", + style="V0.2c+e", + color="black", + frame="af", + ) return fig diff --git a/gmt/tests/test_psconvert.py b/gmt/tests/test_psconvert.py index dd073b939fd..47b915462bd 100644 --- a/gmt/tests/test_psconvert.py +++ b/gmt/tests/test_psconvert.py @@ -11,10 +11,10 @@ def test_psconvert(): psconvert creates a figure in the current directory. """ fig = Figure() - fig.basemap(R='10/70/-3/8', J='X4i/3i', B='a') - prefix = 'test_psconvert' - fig.psconvert(F=prefix, T='f', A=True) - fname = prefix + '.pdf' + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="a") + prefix = "test_psconvert" + fig.psconvert(F=prefix, T="f", A=True) + fname = prefix + ".pdf" assert os.path.exists(fname) os.remove(fname) @@ -24,16 +24,16 @@ def test_psconvert_twice(): Call psconvert twice to get two figures. """ fig = Figure() - fig.basemap(R='10/70/-3/8', J='X4i/3i', B='a') - prefix = 'test_psconvert_twice' + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="a") + prefix = "test_psconvert_twice" # Make a PDF - fig.psconvert(F=prefix, T='f') - fname = prefix + '.pdf' + fig.psconvert(F=prefix, T="f") + fname = prefix + ".pdf" assert os.path.exists(fname) os.remove(fname) # Make a PNG - fig.psconvert(F=prefix, T='g') - fname = prefix + '.png' + fig.psconvert(F=prefix, T="g") + fname = prefix + ".png" assert os.path.exists(fname) os.remove(fname) @@ -43,11 +43,11 @@ def test_psconvert_int_options(): psconvert handles integer options well. """ fig = Figure() - fig.basemap(R='10/70/-3/8', J='X4i/3i', B='a') - prefix = 'test_psconvert_int_options' - fig.psconvert(F=prefix, E=100, T='g', I=True) - assert os.path.exists(prefix + '.png') - os.remove(prefix + '.png') + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="a") + prefix = "test_psconvert_int_options" + fig.psconvert(F=prefix, E=100, T="g", I=True) + assert os.path.exists(prefix + ".png") + os.remove(prefix + ".png") def test_psconvert_aliases(): @@ -55,9 +55,9 @@ def test_psconvert_aliases(): Use the aliases to make sure they work. """ fig = Figure() - fig.basemap(R='10/70/-3/8', J='X4i/3i', B='a') - prefix = 'test_psconvert_aliases' - fig.psconvert(prefix=prefix, fmt='g', crop=True, dpi=100) - fname = prefix + '.png' + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="a") + prefix = "test_psconvert_aliases" + fig.psconvert(prefix=prefix, fmt="g", crop=True, dpi=100) + fname = prefix + ".png" assert os.path.exists(fname) os.remove(fname) diff --git a/gmt/tests/test_session_management.py b/gmt/tests/test_session_management.py index 19b9dfc0f74..b421be8a7c5 100644 --- a/gmt/tests/test_session_management.py +++ b/gmt/tests/test_session_management.py @@ -15,8 +15,8 @@ def test_begin_end(): end() # Kill the global session begin() with LibGMT() as lib: - lib.call_module('psbasemap', '-R10/70/-3/8 -JX4i/3i -Ba') + lib.call_module("psbasemap", "-R10/70/-3/8 -JX4i/3i -Ba") end() begin() # Restart the global session - assert os.path.exists('gmt-python-session.pdf') - os.remove('gmt-python-session.pdf') + assert os.path.exists("gmt-python-session.pdf") + os.remove("gmt-python-session.pdf") diff --git a/gmt/tests/test_which.py b/gmt/tests/test_which.py index 80c6a91d212..c97f49e3082 100644 --- a/gmt/tests/test_which.py +++ b/gmt/tests/test_which.py @@ -11,8 +11,8 @@ def test_which(): "Make sure which returns file paths for @files correctly without errors" - for fname in 'tut_quakes.ngdc tut_bathy.nc'.split(): - cached_file = which('@{}'.format(fname), download='c') + for fname in "tut_quakes.ngdc tut_bathy.nc".split(): + cached_file = which("@{}".format(fname), download="c") assert os.path.exists(cached_file) assert os.path.basename(cached_file) == fname diff --git a/requirements.txt b/requirements.txt index 8b6c07a4fa1..12e162c472a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pytest pytest-cov pytest-mpl coverage -flake8 +black pylint sphinx sphinx_rtd_theme diff --git a/setup.cfg b/setup.cfg index 473e46148bf..e3322cd5914 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,9 +6,5 @@ versionfile_build = gmt/_version.py tag_prefix = '' [flake8] -exclude = - gmt/_version.py -ignore = F401,E226,I101,I100,E741 - -[pep8] -ignore = F401,E226,I101,I100,E741 +ignore = E203, E266, E501, W503, F401, E741 +max-line-length = 88 diff --git a/setup.py b/setup.py index 211de39a857..ed169354d55 100644 --- a/setup.py +++ b/setup.py @@ -8,27 +8,25 @@ import versioneer -NAME = 'gmt-python' -FULLNAME = 'GMT Python' +NAME = "gmt-python" +FULLNAME = "GMT Python" AUTHOR = "Leonardo Uieda" -AUTHOR_EMAIL = 'leouieda@gmail.com' +AUTHOR_EMAIL = "leouieda@gmail.com" MAINTAINER = AUTHOR MAINTAINER_EMAIL = AUTHOR_EMAIL LICENSE = "BSD License" URL = "https://github.com/GenericMappingTools/gmt-python" DESCRIPTION = "A Python interface for the Generic Mapping Tools" -KEYWORDS = '' +KEYWORDS = "" with open("README.rst") as f: - LONG_DESCRIPTION = ''.join(f.readlines()) + LONG_DESCRIPTION = "".join(f.readlines()) VERSION = versioneer.get_version() CMDCLASS = versioneer.get_cmdclass() -PACKAGES = find_packages(exclude=['doc', 'ci']) +PACKAGES = find_packages(exclude=["doc", "ci"]) SCRIPTS = [] -PACKAGE_DATA = { - 'gmt.tests': ['data/*', 'baseline/*'], -} +PACKAGE_DATA = {"gmt.tests": ["data/*", "baseline/*"]} CLASSIFIERS = [ "Development Status :: 3 - Alpha", @@ -42,30 +40,27 @@ "License :: OSI Approved :: {}".format(LICENSE), ] PLATFORMS = "Any" -INSTALL_REQUIRES = [ - 'numpy', - 'pandas', - 'xarray', - 'packaging', -] +INSTALL_REQUIRES = ["numpy", "pandas", "xarray", "packaging"] -if __name__ == '__main__': - setup(name=NAME, - fullname=FULLNAME, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - version=VERSION, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - license=LICENSE, - url=URL, - platforms=PLATFORMS, - scripts=SCRIPTS, - packages=PACKAGES, - package_data=PACKAGE_DATA, - classifiers=CLASSIFIERS, - keywords=KEYWORDS, - install_requires=INSTALL_REQUIRES, - cmdclass=CMDCLASS) +if __name__ == "__main__": + setup( + name=NAME, + fullname=FULLNAME, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + version=VERSION, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, + license=LICENSE, + url=URL, + platforms=PLATFORMS, + scripts=SCRIPTS, + packages=PACKAGES, + package_data=PACKAGE_DATA, + classifiers=CLASSIFIERS, + keywords=KEYWORDS, + install_requires=INSTALL_REQUIRES, + cmdclass=CMDCLASS, + )