diff --git a/.gitignore b/.gitignore index db7f416..64e60dc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ MANIFEST **/*checkpoint.ipynb **/*.ipynb **/*.sas7bcat +sascfg_personal.py +**/*.ipynb_checkpoints/* diff --git a/Readme.md b/Readme.md index 1387599..7ec87cc 100644 --- a/Readme.md +++ b/Readme.md @@ -1,166 +1,145 @@ # SAS Kernel for Jupyter -# Overview +[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jld23/sas_kernel/HEAD) -A SAS Kernel for [Jupyter Notebooks](http://www.jupyter.org) Jupyter Notebooks are capable of running programs in a variety of programming languages and it is the kernel that enables this ability. The SAS kernel enables Jupyter Notebook to provide the following programming experience: -- syntax highlighting for SAS programming statements -- store the input and output from an interactive SAS session +## Overview + +The SAS Kernel for [Jupyter Notebooks](http://www.jupyter.org) is capable of running SAS programs from within the Jupyter interface. +The SAS kernel allows a user to leverage all of the SAS products they have licensed. + +After installing the SAS kernel, you can use a notebook and a SAS installation to write, document, and submit SAS programming statements. The Jupyter notebook interface allows sharing of results through JSON and the SAS kernel is no exception, you can share code and results in a static form through the Jupyter notebook. -After installing the SAS kernel, you can use a notebook and a SAS installation to write, document, and submit SAS programming statements. ## Documentation -Here is the link to the current documentation https://sassoftware.github.io/sas_kernel/ -# Prerequisites -* Python3.X -* Jupyter version 4 or higher -* SAS 9.4 or higher -- This includes [SAS Viya](http://www.sas.com/en_us/software/viya.html) +Here is the link to the current documentation -With the latest changes in [saspy](https://github.com/sassoftware/saspy) it is no longer a requirement that Jupyter and SAS be installed on the same machine. SAS and Jupyter can now communicate via passwordless ssh or IOM. This is in response to [issue 11](https://github.com/sassoftware/sas_kernel/issues/11). The configuration details are located in [sascfg.py](https://github.com/sassoftware/saspy/blob/master/saspy/sascfg.py) and documentation for configuration is [here](https://sassoftware.github.io/saspy/install.html#configuration) +## Prerequisites +- Python3 (this is now the default since Python2 went end of life in January 2020) +- Jupyter version 4 or higher +- SAS 9.4 or higher -- This includes [SAS Viya](http://www.sas.com/en_us/software/viya.html). The SAS kernel is compatable with any version of SAS released since July 2013. +- SASPy -- The SAS kernel has as dependency on [SASPy](https://github.com/sassoftware/saspy). The package will be installed automatically but it must be configured to access your availble SAS server. **SASPy must be [configured](https://sassoftware.github.io/saspy/install.html#configuration) before the SAS kernel can work successfully**. +## Installation -# Installation -To successfully use the SAS Kernel you must have each of the following: -* [SAS version 9.4 or above](http://www.sas.com) -* [Jupyter](http://jupyter.org) - * Jupyter has a number of dependencies. See the subsections for steps on installing Jupyter on your system. -* [Python 3](http://www.python.org) +This will install the SAS Kernel for jupyter as well as the Jupyter lab extensions (jupyterlab v3+ is required) to make you a more productive programmer within Jupyter. [Here are details](https://github.com/jld23/sas_kernel_ext) about the extensions. -## Install for Anaconda Python (assuming SAS already installed) -1. [Download](https://www.continuum.io/downloads) and install Anaconda Python (make sure you get Python3.X). If you install Anaconda without super user rights (root or sudo) then other users on the system will not be able to access the SAS kernel. - A couple notes that I've observed: - * The default install location is the users home directory. This is fine for a single user install I would put it in a common location (`/opt`) if you're doing a system wide install - * One of the prompts is to add the path to your environment. I recommend you want to answer 'yes' to that question so that all the installing user has the executables in their path. If you're doing a system wide install (root or sudo) all the other users should add that path to their environmental variables -1. Install sas_kernel. The sas_kernel has a dependency on saspy which is located [here](https://github.com/sassoftware/saspy). - In the command below I'm assuming that `pip` maps to python3 if that is not the case the you might need to use `pip3` instead. - ``` +```bash +pip install sas_kernel['jlab_ext'] +``` + +### The common methods to install are + +1. `pip` -- PIP is the most common way to install the latest stable version of the code. + + ```bash pip install sas_kernel ``` - -1. Verify that the sas_kernel is installed -`jupyter kernelspec list` - - If you installed as a superuser, your output should similar to this: - ``` - Available kernels: - python3 /opt/Anaconda3-2.5.0/lib/python3.5/site-packages/ipykernel/resources - sas /usr/local/share/jupyter/kernels/sas - ``` - - If you installed as a regular user (sas in this case), your output should similar to this: - ``` - Available kernels: - python3 /home/sas/anaconda3/lib/python3.5/site-packages/ipykernel/resources - sas /home/sas/.local/share/jupyter/kernels/sas - ``` -1. Verify SAS Executable is correct - 1. find the sascfg.py file -- it is currently located in the install location (see above) `[install location]/site-packages/saspy/sascfg.py`. To query `pip` for the location of the file, type `pip show saspy` (or `pip3 show saspy` if `pip3` was used to install it). Failing that, this command will search the OS for the file location: `find / -name sascfg.py` - 1. edit the file with the correct path the SAS executable and include any options you wish it include in the SAS invocation. See examples in the [file](https://github.com/sassoftware/saspy/blob/master/saspy/sascfg.py) - - -## Install for Centos 6 (assuming SAS already installed) -These instructions assume you'll be installed system wide for all users using a superuser account (root or sudo) - -1. yum packages - ``` - sudo yum install https://centos6.iuscommunity.org/ius-release.rpm - sudo yum install python35u gcc-c++ python35u-devel python35u-pip python35u-tools - ``` - -1. pip - ``` - wget https://bootstrap.pypa.io/get-pip.py - python3.5 get-pip.py - pip3 --version - ``` - -1. jupyter and sas_kernel. The sas_kernel has a dependency on saspy which is located [here](https://github.com/sassoftware/saspy). + +1. `conda` -- A conda package is also available if you prefer to use conda as your package manger + + ```bash + conda install -c anaconda sas_kernel ``` - pip3.5 install jupyter - pip3.5 install sas_kernel + +1. From source -- If you need to install from the source branch before a new version has been built and pushed you can install from source like this: + + ```bash + pip install git+https://git@github.com/sassoftware/sas_kernel.git@main ``` - -1. Verify that the sas_kernel is installed -`jupyter kernelspec list` - - This should produce output similar to this: - ``` - Available kernels: - python3 /usr/lib/python3.5/site-packages/ipykernel/resources - sas /usr/local/share/jupyter/kernels/sas - ``` - - -1. Verify SAS Executable is correct - 1. find the sascfg.py file -- it is currently located in the site-packages area of python install. To query `pip` for the location of the file, type `pip show saspy` (or `pip3 show saspy` if `pip3` was used to install it). Failing that, this command will search the OS for the file location: ` find / -name sascfg.py` - 1. edit the file with the correct path the SAS executable and include any options you wish it include in the SAS invocation. See examples in the file - -# Getting Started + + Note that the default branch is now `main` to match the GitHub convention. You can modify the about URL if you're installing from a fork or a non-default branch. + +### To verify that the sas_kernel is installed + +```bash +jupyter kernelspec list +``` + +You should see output _similar_ to code below: + +```bash +Available kernels: + python3 /home/sas/anaconda3/lib/python3.5/site-packages/ipykernel/resources + sas /home/sas/.local/share/jupyter/kernels/sas +``` + +**NOTE:** You will not be able to execute SAS code through Juypter until you have [configured SASPy](https://sassoftware.github.io/saspy/install.html#configuration). + +## Getting Started + Here is a basic example of programming with SAS and Jupyter Notebook: [Getting Started](https://sassoftware.github.io/sas_kernel/getting-started.html) -## Improving Usability + +### Improving Usability + +#### For the Jupyter Lab extensions + +There is a seperate reposity where the extensions are developed and maintained. See [that repo](https://github.com/jld23/sas_kernel_ext) for details + +#### For the Legacy Jupyter Notebook + There are a few NBExtensions that have been created to make working with Jupyter notebooks more productive. These are largely the result of pain points from my use of SAS Kernel for programming tasks. The extensions can be found [here](./sas_kernel/nbextensions). The list includes: -* SAS Log -- which show the SAS log for the last executed cell or the entire log since the last (re)start of the notebook -* themes -- this allows you to change the color scheme for your code to match the traditional SAS color scheme -### Installing the SAS Extensions +- SAS Log -- which show the SAS log for the last executed cell or the entire log since the last (re)start of the notebook +- themes -- this allows you to change the color scheme for your code to match the traditional SAS color scheme + +**NOTE:** These extensions are for Jupyter _Notebook_ they are not compatable with Jupyter _Lab_. Jupyter Lab extensions are in development and will be released shortly. + +#### Installing the SAS Extensions + Details for installing the extensions for SAS can be found [here](./sas_kernel/nbextensions/README.md) -**The install experience was improved with version 1.2** -### Jupyter Magics for the sas_kernel -There are magics that have been written specifically for the sas_kernel to get more details see the [README](./sas_kernel/magics/README.md) +#### Jupyter Magics for the sas_kernel -## Jupyterhub -The SAS kernel can be used with JupyterHub for more information look [here](https://jupyterhub.readthedocs.org/en/latest/) +There are magics that have been written specifically for the sas_kernel to get more details see the [README](./sas_kernel/magics/README.md) -## NBGrader -[nbgrader](http://nbgrader.readthedocs.org/en/stable/) is a system for assigning and grading notebooks and extends jupyter. I have a number of contributions that I'm currently working on in conjunction with teaching SAS programming in a classroom setting. You can see my forked repo [here](https://github.com/jld23/nbgrader) +### NBGrader -# FAQ -* Is there a SAS Magic that I can access from a python kernel? +[nbgrader](http://nbgrader.readthedocs.org/en/stable/) is a system for assigning and grading notebooks and extends jupyter. NBgrader is compatible with the SAS kernel. The work was merged in [September 2020](https://github.com/jupyter/nbgrader/pull/1356). It will be widely available with the next release of NBGrader (0.62), until then you can install from source. - Yes! There are actually several cell magics available from SAS. - They are `%%SAS`, `%%IML`, and `%%OPTMODEL`. To load these magics in your notebook, execute the following command `%load_ext saspy.sas_magic`. You can check that the magics have are successfully activated by looking at the results of `%lsmagic` and looking in the cell magic section. - If you use multiple SAS Cell magics in the *same* notebook they will share a SAS session (have the same WORK libname and MACROS). There is currently no sharing of SAS Sessions between different notebooks. +## FAQ -* Do I need to buy SAS to use this kernel? +- Is there a SAS Magic that I can access from a python kernel? - The SAS Kernel is simply a gateway for Jupyter notebooks to talk to SAS, as such, if SAS is not installed this kernel won't be that helpful. For information on purchasing SAS [click here](http://www.sas.com/en_us/software/how-to-buy.html) + Yes! There are actually several cell magics available from SAS. + They are `%%SAS`, `%%IML`, and `%%OPTMODEL`. To load these magics in your notebook, execute the following command `%load_ext saspy.sas_magic`. You can check that the magics have are successfully activated by looking at the results of `%lsmagic` and looking in the cell magic section. + If you use multiple SAS Cell magics in the _same_ notebook they will share a SAS session (have the same WORK libname and MACROS). There is currently no sharing of SAS Sessions between different notebooks. -* How does Jupyter communicate with SAS? +- Do I need to buy SAS to use this kernel? - Behind a Jupyter notebook is a python session, that python session submits code to SAS and receives responses through socket i/o (leveraging stdin , stdout, and stderr) which has been supported in SAS for a long time + The SAS Kernel is simply a gateway for Jupyter notebooks to talk to SAS, as such, if SAS is not installed this kernel won't be helpful. For information on purchasing SAS [click here](http://www.sas.com/en_us/software/how-to-buy.html) -* If stdin, stdout, and stderr have been supported for so long why do I need to have SAS 9.4 or newer? +- How does Jupyter communicate with SAS? - First, SAS 9.4 was released in July 2013 so it isn't exactly a bleeding edge requirement. The reason for a prerequisite for SAS 9.4 is because that was the first release that supported the creation of HTML5 documents and that is the returned output so that we can render attractive tables and graphs automagically + Behind a Jupyter notebook is a python session, that python session submits code to SAS and receives responses through various pathways (depending on the SASPy configuration). Jupyter can communicate with any SAS host (Windows, Linux, Unix, MVS) that has been released since July 2013 to present. -* How can I see my SAS log, I only see the listing output? +- How can I see my SAS log, I only see the listing output? - SAS is different from many other programming languages in that it has two useful information streams, the log (which details the technical details of what happened and how long it took) and the lst (which includes the tables and graphics from the analysis). The SAS Kernel attempts to show you I *think* you want. Here are the rules: - - LOG|LST|DISPLAYED| NOTES - ---|---|---|--- - Yes|No|LOG|This happens when you run DATA Step or a PROC with the `noprint` option - Yes|Yes|LST|--- - Yes (with ERROR message(s))|Yes|ERROR messages with context from the log, then the listing output|--- - Yes (with ERROR message(s))|No|LOG|--- + SAS is different from many other programming languages in that it has two useful information streams, the log (which details the technical details of what happened and how long it took) and the lst (which includes the tables and graphics from the analysis). The SAS Kernel attempts to show you I _think_ you want. Here are the rules: + | LOG | LST | DISPLAYED | NOTES | + | --------------------------- | --- | ----------------------------------------------------------------- | ----------------------------------------------------------------------- | + | Yes | No | LOG | This happens when you run DATA Step or a PROC with the `noprint` option | + | Yes | Yes | LST | --- | + | Yes (with ERROR message(s)) | Yes | ERROR messages with context from the log, then the listing output | --- | + | Yes (with ERROR message(s)) | No | LOG | --- | - If you want to see the log but it was not displayed you can use [SASLog NBExtension](./sas_kernel/nbextensions/README.md) which will show the log for the last executed cell or the entire log since the last (re)start of the notebook + If you want to see the log but it was not displayed you can use [SASLog NBExtension](./sas_kernel/nbextensions/README.md) which will show the log for the last executed cell or the entire log since the last (re)start of the notebook -* Will this leave a bunch of SAS sessions hanging around? +- Will this leave a bunch of SAS sessions hanging around? - A SAS session is started for each notebook you have open i.e. 5 notebooks open = 5 SAS sessions. Those sessions will remain active for the life of the notebook. If you shutdown your notebook, the SAS session will also terminate. In Jupyterhub, there are configuration options to shutdown inactive sessions and the SAS kernel complies with those directives. + A SAS session is started for each notebook you have open i.e. 5 notebooks open = 5 SAS sessions. Those sessions will remain active for the life of the notebook. If you shutdown your notebook, the SAS session will also terminate. In Jupyterhub, there are configuration options to shutdown inactive sessions and the SAS kernel complies with those directives. -* I restarted my SAS Kernel and now my WORK library is now empty. What happened? +- I restarted my SAS Kernel and now my WORK library is now empty. What happened? - When you restart the kernel in a notebook you are terminating the current SAS session and starting a new one. All of the temporary artifacts, data sets in the WORK library, assigned libnames, filename, WORK macros, and so on are destroyed. + When you restart the kernel in a notebook you are terminating the current SAS session and starting a new one. All of the temporary artifacts, data sets in the WORK library, assigned libnames, filename, WORK macros, and so on are destroyed. ## Contributing + The [Contributor Agreement](https://github.com/sassoftware/sas_kernel/blob/master/ContributorAgreement.txt) details how contributions can be made. ## Licensing -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at [LICENSE.txt](https://github.com/sassoftware/sas_kernel/blob/master/LICENSE.txt) -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at [LICENSE.txt](https://github.com/sassoftware/sas_kernel/blob/master/LICENSE.txt) +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/binder/environment.yml b/binder/environment.yml new file mode 100644 index 0000000..cfd6497 --- /dev/null +++ b/binder/environment.yml @@ -0,0 +1,11 @@ +name: sas-compute-env +channels: + - conda-forge +dependencies: + - python + - pip + - jupyterlab=3 + - pip: + - saspy + - sas_kernel['jlab_ext'] + - pandas diff --git a/binder/postBuild b/binder/postBuild new file mode 100644 index 0000000..c4da38a --- /dev/null +++ b/binder/postBuild @@ -0,0 +1,34 @@ +#!/bin/bash + +# +# Copyright SAS Institute +# +# Licensed under the Apache License, Version 2.0 (the License); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# copy sascfg_personal.py to default location +mkdir -p ~/.config/saspy/ +cp ./binder/sascfg_personal.py ~/.config/saspy/ + +#set -ex +jupyter kernelspec list +jupyter nbextension list + + +jupyter nbextension install --py sas_kernel.showSASLog --user +jupyter nbextension enable sas_kernel.showSASLog --py + +jupyter nbextension install --py sas_kernel.theme --user +jupyter nbextension enable sas_kernel.theme --py + +jupyter nbextension list diff --git a/binder/sascfg_personal.py b/binder/sascfg_personal.py new file mode 100644 index 0000000..f73a75c --- /dev/null +++ b/binder/sascfg_personal.py @@ -0,0 +1,8 @@ +SAS_config_names = ['viya4'] + +viya4 = { + 'url':'https://temp.url.com:443', + 'verify': False, + 'timeout': 30, + 'context':'SAS Job Execution compute context' + } diff --git a/sas_kernel/__init__.py b/sas_kernel/__init__.py index 060e413..d37b2d2 100644 --- a/sas_kernel/__init__.py +++ b/sas_kernel/__init__.py @@ -15,4 +15,3 @@ """SAS Kernel Juypter Implementation""" -from sas_kernel.version import __version__ diff --git a/sas_kernel/__main__.py b/sas_kernel/__main__.py index b13e2d8..74cdf70 100644 --- a/sas_kernel/__main__.py +++ b/sas_kernel/__main__.py @@ -16,5 +16,4 @@ from ipykernel.kernelapp import IPKernelApp from .kernel import SASKernel - -IPKernelApp.launch_instance(kernel_class=SASKernel) \ No newline at end of file +IPKernelApp.launch_instance(kernel_class=SASKernel) diff --git a/sas_kernel/version.py b/sas_kernel/_version.py similarity index 96% rename from sas_kernel/version.py rename to sas_kernel/_version.py index 22417d3..4fe3d28 100644 --- a/sas_kernel/version.py +++ b/sas_kernel/_version.py @@ -14,5 +14,5 @@ # limitations under the License. # +__version__ = '2.4.1' -__version__ = '2.3.1' diff --git a/sas_kernel/kernel.py b/sas_kernel/kernel.py index fa6bef0..af45e1a 100644 --- a/sas_kernel/kernel.py +++ b/sas_kernel/kernel.py @@ -13,27 +13,32 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import base64 import os +import sys import re import json -# Create Logger +import types +import importlib.machinery +# Create LOGGER import logging +import saspy from typing import Tuple - -from metakernel import MetaKernel -from sas_kernel.version import __version__ from IPython.display import HTML +from metakernel import MetaKernel +from ._version import __version__ + + +# create a LOGGER to output messages to the Jupyter CONSOLE +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.WARN) +CONSOLE = logging.StreamHandler() +CONSOLE.setFormatter(logging.Formatter('%(name)-12s: %(message)s')) +LOGGER.addHandler(CONSOLE) + +LOGGER.debug("sanity check") -# create a logger to output messages to the Jupyter console -logger = logging.getLogger(__name__) -logger.setLevel(logging.WARN) -console = logging.StreamHandler() -console.setFormatter(logging.Formatter('%(name)-12s: %(message)s')) -logger.addHandler(console) -logger.debug("sanity check") class SASKernel(MetaKernel): """ SAS Kernel for Jupyter implementation. This module relies on SASPy @@ -50,9 +55,11 @@ class SASKernel(MetaKernel): } def __init__(self, **kwargs): - with open(os.path.dirname(os.path.realpath(__file__)) + '/data/' + 'sasproclist.json') as proclist: + with open(os.path.dirname(os.path.realpath(__file__)) + \ + '/data/' + 'sasproclist.json') as proclist: self.proclist = json.load(proclist) - with open(os.path.dirname(os.path.realpath(__file__)) + '/data/' + 'sasgrammardictionary.json') as compglo: + with open(os.path.dirname(os.path.realpath(__file__)) + \ + '/data/' + 'sasgrammardictionary.json') as compglo: self.compglo = json.load(compglo) self.strproclist = '\n'.join(str(x) for x in self.proclist) self.promptDict = {} @@ -60,7 +67,7 @@ def __init__(self, **kwargs): self.mva = None self.cachedlog = None self.lst_len = -99 # initialize the length to a negative number to trigger function - # print(dir(self)) + self._allow_stdin = False def do_apply(self, content, bufs, msg_id, reply_metadata): pass @@ -71,6 +78,15 @@ def do_clear(self): def get_usage(self): return "This is the SAS kernel." + def _get_config_names(self): + """ + get the config file used by SASPy + """ + loader = importlib.machinery.SourceFileLoader('foo', saspy.SAScfg) + cfg = types.ModuleType(loader.name) + loader.exec_module(cfg) + return cfg.SAS_config_names + def _get_lst_len(self): code = "data _null_; run;" res = self.mva.submit(code) @@ -81,11 +97,25 @@ def _get_lst_len(self): def _start_sas(self): try: - import saspy as saspy + # import saspy as saspy self.mva = saspy.SASsession(kernel=self) - except: + except KeyError: + self.mva = None + except OSError:#socket.gaierror + msg = """Failed to connect to SAS! + Please check your connection configuration here:{0} + Here are the valid configurations:{1} + You can load the configuration file into a Jupyter Lab cell using this command: + %load {0} + If the URL/Path are correct the issue is likely your username and/or password + """.format(saspy.list_configs()[0], ', '.join(self._get_config_names())) + self.Error_display(msg) self.mva = None - + except: + print("Unexpected error:", sys.exc_info()[0]) + raise + + def _colorize_log(self, log: str) -> str: """ takes a SAS log (str) and then looks for errors. @@ -109,7 +139,6 @@ def _colorize_log(self, log: str) -> str: return colored_log - def _is_error_log(self, log: str) -> Tuple: """ takes a SAS log (str) and then looks for errors. @@ -120,17 +149,17 @@ def _is_error_log(self, log: str) -> Tuple: error_log_msg_list = [] error_log_line_list = [] for index, line in enumerate(lines): - #logger.debug("line:{}".format(line)) + # LOGGER.debug("line:{}".format(line)) if line.startswith('ERROR'): - error_count +=1 - error_log_msg_list.append(line) - error_log_line_list.append(index) - return (error_count, error_log_msg_list, error_log_line_list) - + error_count += 1 + error_log_msg_list.append(line) + error_log_line_list.append(index) + return (error_count, error_log_msg_list, error_log_line_list) def _which_display(self, log: str, output: str = '') -> str: """ - Determines if the log or lst should be returned as the results for the cell based on parsing the log + Determines if the log or lst should be returned as the + results for the cell based on parsing the log looking for errors and the presence of lst output. :param log: str log from code submission @@ -138,31 +167,36 @@ def _which_display(self, log: str, output: str = '') -> str: :return: The correct results based on log and lst :rtype: str """ - error_count, msg_list, error_line_list = self._is_error_log(log) + error_count, msg_list, error_line_list = self._is_error_log(log) # store the log for display in the showSASLog nbextension - self.cachedlog = self._colorize_log(log) - - if error_count == 0 and len(output) > self.lst_len: # no error and LST output + #self.cachedlog = self._colorize_log(log) + + # no error and LST output + if error_count == 0 and len(output) > self.lst_len: return self.Display(HTML(output)) elif error_count > 0 and len(output) > self.lst_len: # errors and LST - #filter log to lines around first error + # filter log to lines around first error # by default get 5 lines on each side of the first Error message. # to change that modify the values in {} below regex_around_error = r"(.*)(.*\n){6}^ERROR(.*\n){6}" - + # Extract the first match +/- 5 lines e_log = re.search(regex_around_error, log, re.MULTILINE).group() - assert error_count == len(error_line_list), "Error count and count of line number don't match" - return self.Error_display(msg_list[0], print(self._colorize_log(e_log)), HTML(output)) - + assert error_count == len( + error_line_list), "Error count and count of line number don't match" + return self.Error_display(msg_list[0], + print(self._colorize_log(e_log)), + HTML(output)) + # for everything else return the log return self.Print(self._colorize_log(log)) def do_execute_direct(self, code: str, silent: bool = False) -> [str, dict]: """ - This is the main method that takes code from the Jupyter cell and submits it to the SAS server + This is the main method that takes code from the Jupyter cell + and submits it to the SAS server. :param code: code from the cell :param silent: @@ -182,10 +216,8 @@ def do_execute_direct(self, code: str, silent: bool = False) -> [str, dict]: self._get_lst_len() # This block uses special strings submitted by the Jupyter notebook extensions - if code.startswith('showSASLog_11092015') == False and code.startswith("CompleteshowSASLog_11092015") == False: - logger.debug("code type: " + str(type(code))) - logger.debug("code length: " + str(len(code))) - logger.debug("code string: " + code) + if not code.startswith('showSASLog_11092015') and \ + not code.startswith("CompleteshowSASLog_11092015"): if code.startswith("/*SASKernelTest*/"): res = self.mva.submit(code, "text") else: @@ -195,20 +227,23 @@ def do_execute_direct(self, code: str, silent: bool = False) -> [str, dict]: print(res['LOG'], '\n' "Restarting SAS session on your behalf") self.do_shutdown(True) return res['LOG'] - + + # store the log for display in the showSASLog nbextension + self.cachedlog = self._colorize_log(res['LOG']) + # Parse the log to check for errors error_count, error_log_msg, _ = self._is_error_log(res['LOG']) - + if error_count > 0 and len(res['LST']) <= self.lst_len: - return(self.Error(error_log_msg[0], print(self._colorize_log(res['LOG'])))) + return self.Error(error_log_msg[0], print(self._colorize_log(res['LOG']))) return self._which_display(res['LOG'], res['LST']) - elif code.startswith("CompleteshowSASLog_11092015") == True and code.startswith('showSASLog_11092015') == False: - return (self.Print(self._colorize_log(self.mva.saslog()))) + elif code.startswith("CompleteshowSASLog_11092015") and \ + not code.startswith('showSASLog_11092015'): + return self.Print(self._colorize_log(self.mva.saslog())) else: - return (self.Print(self._colorize_log(self.cachedlog))) - + return self.Print(self._colorize_log(self.cachedlog)) def get_completions(self, info): """ @@ -220,7 +255,8 @@ def get_completions(self, info): relstart = info['start'] seg = info['line'][:relstart] if relstart > 0 and re.match('(?i)proc', seg.rsplit(None, 1)[-1]): - potentials = re.findall('(?i)^' + info['obj'] + '.*', self.strproclist, re.MULTILINE) + potentials = re.findall( + '(?i)^' + info['obj'] + '.*', self.strproclist, re.MULTILINE) return potentials else: lastproc = info['code'].lower()[:info['help_pos']].rfind('proc') @@ -241,10 +277,11 @@ def get_completions(self, info): mykey = 's' if lastproc > lastsemi: mykey = 'p' - procer = re.search('(?i)proc\s\w+', info['code'][lastproc:]) + procer = re.search(r'(?i)proc\s\w+', info['code'][lastproc:]) method = procer.group(0).split(' ')[-1].upper() + mykey mylist = self.compglo[method][0] - potentials = re.findall('(?i)' + info['obj'] + '.+', '\n'.join(str(x) for x in mylist), re.MULTILINE) + potentials = re.findall( + '(?i)' + info['obj'] + '.+', '\n'.join(str(x) for x in mylist), re.MULTILINE) return potentials elif data: # we are in statements (probably if there is no data) @@ -255,7 +292,8 @@ def get_completions(self, info): if lastproc > lastsemi: mykey = 'p' mylist = self.compglo['DATA' + mykey][0] - potentials = re.findall('(?i)^' + info['obj'] + '.*', '\n'.join(str(x) for x in mylist), re.MULTILINE) + potentials = re.findall( + '(?i)^' + info['obj'] + '.*', '\n'.join(str(x) for x in mylist), re.MULTILINE) return potentials else: potentials = [''] @@ -263,22 +301,22 @@ def get_completions(self, info): @staticmethod def _get_right_list(s): - proc_opt = re.search(r"proc\s(\w+).*?[^;]\Z", s, re.IGNORECASE | re.MULTILINE) - proc_stmt = re.search(r"\s*proc\s*(\w+).*;.*\Z", s, re.IGNORECASE | re.MULTILINE) - data_opt = re.search(r"\s*data\s*[^=].*[^;]?.*$", s, re.IGNORECASE | re.MULTILINE) - data_stmt = re.search(r"\s*data\s*[^=].*[^;]?.*$", s, re.IGNORECASE | re.MULTILINE) + proc_opt = re.search( + r"proc\s(\w+).*?[^;]\Z", s, re.IGNORECASE | re.MULTILINE) + proc_stmt = re.search(r"\s*proc\s*(\w+).*;.*\Z", + s, re.IGNORECASE | re.MULTILINE) + data_opt = re.search( + r"\s*data\s*[^=].*[^;]?.*$", s, re.IGNORECASE | re.MULTILINE) + data_stmt = re.search( + r"\s*data\s*[^=].*[^;]?.*$", s, re.IGNORECASE | re.MULTILINE) print(s) if proc_opt: - logger.debug(proc_opt.group(1).upper() + 'p') return proc_opt.group(1).upper() + 'p' elif proc_stmt: - logger.debug(proc_stmt.group(1).upper() + 's') return proc_stmt.group(1).upper() + 's' elif data_opt: - logger.debug("data step") return 'DATA' + 'p' elif data_stmt: - logger.debug("data step") return 'DATA' + 's' else: return None @@ -306,9 +344,3 @@ def do_shutdown(self, restart): self.restart_kernel() self.Print("Done!") return {'status': 'ok', 'restart': restart} - - -if __name__ == '__main__': - from ipykernel.kernelapp import IPKernelApp - from .kernel import SASKernel - IPKernelApp.launch_instance(kernel_class=SASKernel) diff --git a/setup.py b/setup.py index e19d25a..2f44950 100644 --- a/setup.py +++ b/setup.py @@ -22,12 +22,11 @@ import os import sys -from sas_kernel.version import __version__ - -svem_flag = '--single-version-externally-managed' -if svem_flag in sys.argv: - sys.argv.remove(svem_flag) +from sas_kernel._version import __version__ +SVEM_FLAG = '--single-version-externally-managed' +if SVEM_FLAG in sys.argv: + sys.argv.remove(SVEM_FLAG) class InstallWithKernelspec(install): def run(self): @@ -53,16 +52,26 @@ def run(self): url='https://github.com/sassoftware/sas_kernel', packages=find_packages(), cmdclass={'install': InstallWithKernelspec}, - package_data={'': ['*.js', '*.md', '*.yaml', '*.css'], 'sas_kernel': ['data/*.json', 'data/*.png']}, - install_requires=['saspy>=3', "metakernel>=0.27.0", "jupyter_client >=4.4.0", + package_data={'': ['*.js', '*.md', '*.yaml', '*.css'], + 'sas_kernel': ['data/*.json', 'data/*.png']}, + install_requires=['saspy>=3.6', "metakernel>=0.27.5", "jupyter_client >=6", "ipython>=5.0.0" ], + extras_require={'jlab_ext': ['jupyterlab >=3 ', + 'jlab_create_sas_file', + 'sas2nb', + 'sas_log_viewer_v2']}, classifiers=['Framework :: IPython', 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3', "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + 'Intended Audience :: Science/Research', + 'Intended Audience :: Developers', + 'Operating System :: OS Independent', + 'Topic :: Software Development', "Topic :: System :: Shells"] )