Merged PR 131145: [Jupyter Wrapper]: Initial code structure

Initial code structure for powerbi Jupyter widget

This is based on official boilerplate: https://github.com/jupyter-widgets/widget-ts-cookiecutter

Related work items: #473925
This commit is contained in:
Anant Singh 2020-12-10 10:37:45 +00:00 коммит произвёл Mayur Garhwal
Родитель b9bedf8941
Коммит 1990df9355
57 изменённых файлов: 8186 добавлений и 16 удалений

2
.coveragerc Normal file
Просмотреть файл

@ -0,0 +1,2 @@
[run]
omit = powerbi_widget/tests/*

5
.eslintignore Normal file
Просмотреть файл

@ -0,0 +1,5 @@
node_modules
dist
coverage
**/*.d.ts
tests

28
.eslintrc.js Normal file
Просмотреть файл

@ -0,0 +1,28 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/quotes': [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false }
],
curly: ['error', 'all'],
eqeqeq: 'error',
'prefer-arrow-callback': 'error'
}
};

156
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,156 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask instance folder
instance/
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
docs/source/_static/embed-bundle.js
docs/source/_static/embed-bundle.js.map
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# NPM
# ----
**/node_modules/
powerbi_widget/nbextension/static/index.*
powerbi_widget/labextension/*.tgz
# Coverage data
# -------------
**/coverage/
# Packed lab extensions
powerbi_widget/labextension

7
.npmignore Normal file
Просмотреть файл

@ -0,0 +1,7 @@
.DS_Store
node_modules/
tests/
.jshintrc
# Ignore any build output from python:
dist/*.tar.gz
dist/*.wheel

4
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,4 @@
node_modules
**/node_modules
**/lib
**/package.json

3
.prettierrc Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"singleQuote": true
}

74
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,74 @@
language: python
python:
- 3.7
- 3.6
- 3.5
sudo: false
dist: xenial
services:
- xvfb
addons:
apt_packages:
- pandoc
env:
matrix:
- GROUP=python
matrix:
include:
- python: 3.5
env: GROUP=js
include:
- python: 3.6
env: GROUP=docs
cache:
pip: true
directories:
- node_modules # NPM packages
- $HOME/.npm
before_install:
- pip install -U pip setuptools
- nvm install 12
- |
if [[ $GROUP == python ]]; then
pip install codecov
elif [[ $GROUP == js ]]; then
npm install -g codecov
fi
install:
- |
if [[ $GROUP == python ]]; then
pip install --upgrade ".[test]" -v
elif [[ $GROUP == js ]]; then
pip install --upgrade -e ".[test]" -v
elif [[ $GROUP == docs ]]; then
pip install --upgrade ".[test, examples, docs]" -v
fi
before_script:
# Set up a virtual screen for Firefox browser testing:
- |
if [[ $GROUP == js ]]; then
export CHROME_BIN=chromium-browser
fi
git config --global user.email travis@fake.com
git config --global user.name "Travis CI"
script:
- |
if [[ $GROUP == python ]]; then
EXIT_STATUS=0
pushd $(mktemp -d)
py.test -l --cov-report xml:$TRAVIS_BUILD_DIR/coverage.xml --cov=powerbi_widget --pyargs powerbi_widget || EXIT_STATUS=$?
popd
(exit $EXIT_STATUS)
elif [[ $GROUP == js ]]; then
npm test
elif [[ $GROUP == docs ]]; then
EXIT_STATUS=0
cd docs
make html || EXIT_STATUS=$?
make linkcheck || EXIT_STATUS=$?
cd ..
python -m pytest_check_links --links-ext=.md -o testpaths=. -o addopts= || EXIT_STATUS=$?
(exit $EXIT_STATUS)
fi
after_success:
- codecov

27
LICENSE.txt Normal file
Просмотреть файл

@ -0,0 +1,27 @@
Copyright (c) 2020 Microsoft
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

40
MANIFEST.in Normal file
Просмотреть файл

@ -0,0 +1,40 @@
include LICENSE.txt
include README.md
include setupbase.py
include pytest.ini
include .coverage.rc
include tsconfig.json
include package.json
include webpack.config.js
include powerbi_widget/labextension/*.tgz
# Documentation
graft docs
exclude docs/\#*
prune docs/build
prune docs/gh-pages
prune docs/dist
# Examples
graft examples
# Tests
graft tests
prune tests/build
# Javascript files
graft powerbi_widget/nbextension
graft src
graft css
prune **/node_modules
prune coverage
prune lib
# Patterns to exclude from any directory
global-exclude *~
global-exclude *.pyc
global-exclude *.pyo
global-exclude .git
global-exclude .ipynb_checkpoints

Просмотреть файл

@ -1,20 +1,76 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# powerbi-jupyter
# Build and Test
TODO: Describe and show how to build your code and run the tests.
A Custom Jupyter Widget Library
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
## Installation
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
You can install using `pip`:
```bash
pip install powerbi_widget
```
Or if you use jupyterlab:
```bash
pip install powerbi_widget
jupyter labextension install @jupyter-widgets/jupyterlab-manager
```
If you are using Jupyter Notebook 5.2 or earlier, you may also need to enable
the nbextension:
```bash
jupyter nbextension enable --py [--sys-prefix|--user|--system] powerbi_widget
```
## Development Installation
```bash
# First install the python package. This will also build the JS packages.
pip install -e ".[test, examples]"
```
When developing your extensions, you need to manually enable your extensions with the
notebook / lab frontend. For lab, this is done by the command:
```
jupyter labextension install @jupyter-widgets/jupyterlab-manager --no-build
jupyter labextension install .
```
For classic notebook, you can run:
```
jupyter nbextension install --sys-prefix --symlink --overwrite --py powerbi_widget
jupyter nbextension enable --sys-prefix --py powerbi_widget
```
__Note__ that the `--symlink` flag doesn't work on Windows, so you will here have to run
the `install` command every time that you rebuild your extension. For certain installations
you might also need another flag instead of `--sys-prefix`, but we won't cover the meaning
of those flags here.
### How to see your changes
#### Typescript:
To continuously monitor the project for changes and automatically trigger a rebuild, start Jupyter in watch mode:
```bash
jupyter lab --watch
```
To execute the project in Jupyter Notebook:
```bash
python setup.py sdist
jupyter notebook
```
And in a separate session, begin watching the source directory for changes:
```bash
npm run watch
```
After a change wait for the build to finish and then refresh your browser and the changes should take effect.
#### Python:
If you make a change to the python code then you will need to restart the notebook kernel to have it take effect.

62
appveyor.yml Normal file
Просмотреть файл

@ -0,0 +1,62 @@
# Do not build feature branch with open Pull Requests
skip_branch_with_pr: true
# environment variables
environment:
nodejs_version: "8"
matrix:
- PYTHON: "C:\\Miniconda3-x64"
PYTHON_VERSION: "3.7"
PYTHON_MAJOR: 3
PYTHON_ARCH: "64"
- PYTHON: "C:\\Miniconda3"
PYTHON_VERSION: "3.4"
PYTHON_MAJOR: 3
PYTHON_ARCH: "32"
# build cache to preserve files/folders between builds
cache:
- '%AppData%/npm-cache'
- '%PYTHON%/pkgs'
- '%LOCALAPPDATA%\pip\Cache'
# scripts that run after cloning repository
install:
# Install node:
- ps: Install-Product node $env:nodejs_version
# Ensure python scripts are from right version:
- 'SET "PATH=%PYTHON%\Scripts;%PYTHON%;%PATH%"'
# Setup conda:
- 'conda list'
- 'conda update conda -y'
# If 32 bit, force conda to use it:
- 'IF %PYTHON_ARCH% EQU 32 SET CONDA_FORCE_32BIT=1'
- 'conda create -n test_env python=%PYTHON_VERSION% -y'
- 'activate test_env'
# Update install tools:
- 'conda install setuptools pip -y'
- 'python -m pip install --upgrade pip'
- 'python -m easy_install --upgrade setuptools'
# Install coverage utilities:
- 'pip install codecov'
# Install our package:
- 'pip install --upgrade ".[test]" -v'
build: off
# scripts to run before tests
before_test:
- git config --global user.email appveyor@fake.com
- git config --global user.name "AppVeyor CI"
- set "tmptestdir=%tmp%\powerbi_widget-%RANDOM%"
- mkdir "%tmptestdir%"
- cd "%tmptestdir%"
# to run your custom scripts instead of automatic tests
test_script:
- 'py.test -l --cov-report xml:"%APPVEYOR_BUILD_FOLDER%\coverage.xml" --cov=powerbi_widget --pyargs powerbi_widget'
on_success:
- cd "%APPVEYOR_BUILD_FOLDER%"
- codecov -X gcov --file "%APPVEYOR_BUILD_FOLDER%\coverage.xml"

12
codecov.yml Normal file
Просмотреть файл

@ -0,0 +1,12 @@
comment: off
# show coverage in CI status, but never consider it a failure
coverage:
status:
project:
default:
target: 0%
patch:
default:
target: 0%
ignore:
- "powerbi_widget/tests"

4
css/widget.css Normal file
Просмотреть файл

@ -0,0 +1,4 @@
.custom-widget {
background-color: lightseagreen;
padding: 0px 2px;
}

20
docs/Makefile Normal file
Просмотреть файл

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = powerbi_widget
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

11
docs/environment.yml Normal file
Просмотреть файл

@ -0,0 +1,11 @@
name: powerbi_widget_docs
channels:
- conda-forge
dependencies:
- python=3.5
- nodejs
- numpy
- sphinx
- nbsphinx
- jupyter_sphinx

36
docs/make.bat Normal file
Просмотреть файл

@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
set SPHINXPROJ=powerbi_widget
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

Просмотреть файл

@ -0,0 +1,5 @@
var cache_require = window.require;
window.addEventListener('load', function() {
window.require = cache_require;
});

209
docs/source/conf.py Normal file
Просмотреть файл

@ -0,0 +1,209 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# powerbi_widget documentation build configuration file
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinx.ext.todo',
'nbsphinx',
'jupyter_sphinx.execute',
'nbsphinx_link',
]
# Ensure our extension is available:
import sys
from os.path import dirname, join as pjoin
docs = dirname(dirname(__file__))
root = dirname(docs)
sys.path.insert(0, root)
sys.path.insert(0, pjoin(docs, 'sphinxext'))
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'powerbi_widget'
copyright = '2020, Microsoft'
author = 'Microsoft'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# get version from python package:
import os
here = os.path.dirname(__file__)
repo = os.path.join(here, '..', '..')
_version_py = os.path.join(repo, 'powerbi_widget', '_version.py')
version_ns = {}
with open(_version_py) as f:
exec(f.read(), version_ns)
# The short X.Y version.
version = '%i.%i' % version_ns['version_info'][:2]
# The full version, including alpha/beta/rc tags.
release = version_ns['__version__']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['**.ipynb_checkpoints']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'powerbi_widgetdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'powerbi_widget.tex', 'powerbi_widget Documentation',
'Microsoft', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc,
'powerbi_widget',
'powerbi_widget Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc,
'powerbi_widget',
'powerbi_widget Documentation',
author,
'powerbi_widget',
'A Custom Jupyter Widget Library',
'Miscellaneous'),
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
# Read The Docs
# on_rtd is whether we are on readthedocs.org, this line of code grabbed from
# docs.readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# Uncomment this line if you have know exceptions in your included notebooks
# that nbsphinx complains about:
#
nbsphinx_allow_errors = True # exception ipstruct.py ipython_genutils
from sphinx.util import logging
logger = logging.getLogger(__name__)
def setup(app):
def add_scripts(app):
for fname in ['helper.js', 'embed-bundle.js']:
if not os.path.exists(os.path.join(here, '_static', fname)):
logger.warning('missing javascript file: %s' % fname)
app.add_js_file(fname)
app.connect('builder-inited', add_scripts)

Просмотреть файл

@ -0,0 +1,31 @@
Developer install
=================
To install a developer version of powerbi_widget, you will first need to clone
the repository::
git clone https://github.com/Microsoft/powerbi-jupyter
cd powerbi-jupyter
Next, install it with a develop install using pip::
pip install -e .
If you are planning on working on the JS/frontend code, you should also do
a link installation of the extension::
jupyter nbextension install [--sys-prefix / --user / --system] --symlink --py powerbi_widget
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbi_widget
with the `appropriate flag`_. Or, if you are using Jupyterlab::
jupyter labextension install .
.. links
.. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions

Просмотреть файл

@ -0,0 +1,18 @@
Examples
========
This section contains several examples generated from Jupyter notebooks.
The widgets have been embedded into the page for demonstrative pruposes.
.. todo::
Add links to notebooks in examples folder similar to the initial
one. This is a manual step to ensure only those examples that
are suited for inclusion are used.
.. toctree::
:glob:
*

Просмотреть файл

@ -0,0 +1,3 @@
{
"path": "../../../examples/introduction.ipynb"
}

49
docs/source/index.rst Normal file
Просмотреть файл

@ -0,0 +1,49 @@
powerbi_widget
=====================================
Version: |release|
A Custom Jupyter Widget Library
Quickstart
----------
To get started with powerbi_widget, install with pip::
pip install powerbi_widget
or with conda::
conda install powerbi_widget
Contents
--------
.. toctree::
:maxdepth: 2
:caption: Installation and usage
installing
introduction
.. toctree::
:maxdepth: 1
examples/index
.. toctree::
:maxdepth: 2
:caption: Development
develop-install
.. links
.. _`Jupyter widgets`: https://jupyter.org/widgets.html
.. _`notebook`: https://jupyter-notebook.readthedocs.io/en/latest/

Просмотреть файл

@ -0,0 +1,37 @@
.. _installation:
Installation
============
The simplest way to install powerbi_widget is via pip::
pip install powerbi_widget
or via conda::
conda install powerbi_widget
If you installed via pip, and notebook version < 5.3, you will also have to
install / configure the front-end extension as well. If you are using classic
notebook (as opposed to Jupyterlab), run::
jupyter nbextension install [--sys-prefix / --user / --system] --py powerbi_widget
jupyter nbextension enable [--sys-prefix / --user / --system] --py powerbi_widget
with the `appropriate flag`_. If you are using Jupyterlab, install the extension
with::
jupyter labextension install powerbi-widget-client
If you are installing using conda, these commands should be unnecessary, but If
you need to run them the commands should be the same (just make sure you choose the
`--sys-prefix` flag).
.. links
.. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions

Просмотреть файл

@ -0,0 +1,7 @@
=============
Introduction
=============
.. todo::
add prose explaining project purpose and usage here

Просмотреть файл

@ -0,0 +1,60 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Introduction"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import powerbi_widget"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"w = powerbi_widget.ExampleWidget()\n",
"w"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert w.value == 'Hello World'"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

5548
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

94
package.json Normal file
Просмотреть файл

@ -0,0 +1,94 @@
{
"name": "powerbi-widget-client",
"version": "0.1.0",
"description": "A Custom Jupyter Widget Library",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension",
"widgets"
],
"files": [
"lib/**/*.js",
"dist/*.js",
"css/*.css"
],
"homepage": "https://github.com/Microsoft/powerbi-jupyter",
"bugs": {
"url": "https://github.com/Microsoft/powerbi-jupyter/issues"
},
"license": "BSD-3-Clause",
"author": {
"name": "Microsoft"
},
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/powerbi-jupyter"
},
"scripts": {
"build": "npm run build:lib && npm run build:nbextension",
"build:labextension": "npm run clean:labextension && mkdirp powerbi_widget/labextension && cd powerbi_widget/labextension && npm pack ../..",
"build:lib": "tsc",
"build:nbextension": "webpack -p",
"build:all": "npm run build:labextension && npm run build:nbextension",
"clean": "npm run clean:lib && npm run clean:nbextension",
"clean:lib": "rimraf lib",
"clean:labextension": "rimraf powerbi_widget/labextension",
"clean:nbextension": "rimraf powerbi_widget/nbextension/static/index.js",
"lint": "eslint . --ext .ts,.tsx --fix",
"lint:check": "eslint . --ext .ts,.tsx",
"prepack": "npm run build:lib",
"test": "npm run test:firefox",
"test:chrome": "karma start --browsers=Chrome tests/karma.conf.js",
"test:debug": "karma start --browsers=Chrome --singleRun=false --debug=true tests/karma.conf.js",
"test:firefox": "karma start --browsers=Firefox tests/karma.conf.js",
"test:ie": "karma start --browsers=IE tests/karma.conf.js",
"watch": "npm-run-all -p watch:*",
"watch:lib": "tsc -w",
"watch:nbextension": "webpack --watch"
},
"dependencies": {
"@jupyter-widgets/base": "^1.1.10 || ^2 || ^3"
},
"devDependencies": {
"@phosphor/application": "^1.6.0",
"@phosphor/widgets": "^1.6.0",
"@types/expect.js": "^0.3.29",
"@types/mocha": "^5.2.5",
"@types/node": "^10.11.6",
"@types/webpack-env": "^1.13.6",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"acorn": "^7.2.0",
"css-loader": "^3.2.0",
"eslint": "^7.4.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"expect.js": "^0.3.1",
"fs-extra": "^7.0.0",
"karma": "^3.1.0",
"karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.1.0",
"karma-ie-launcher": "^1.0.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-typescript": "^5.0.3",
"karma-typescript-es6-transform": "^5.0.3",
"mkdirp": "^0.5.1",
"mocha": "^5.2.0",
"npm-run-all": "^4.1.3",
"prettier": "^2.0.5",
"rimraf": "^2.6.2",
"source-map-loader": "^0.2.4",
"style-loader": "^1.0.0",
"ts-loader": "^5.2.1",
"typescript": "~3.8",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2"
},
"jupyterlab": {
"extension": "lib/plugin"
}
}

5
powerbi_widget.json Normal file
Просмотреть файл

@ -0,0 +1,5 @@
{
"load_extensions": {
"powerbi_widget/extension": true
}
}

Просмотреть файл

@ -0,0 +1,12 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# TODO: Remove example import
from .example import ExampleWidget
from .report import Report
from ._version import __version__, version_info
from .nbextension import _jupyter_nbextension_paths

Просмотреть файл

@ -0,0 +1,11 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
"""
Information about the frontend package of the widgets.
"""
module_name = "powerbi-widget-client"
module_version = "^0.1.0"

Просмотреть файл

@ -0,0 +1,7 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
version_info = (0, 1, 0, 'dev')
__version__ = ".".join(map(str, version_info))

25
powerbi_widget/example.py Normal file
Просмотреть файл

@ -0,0 +1,25 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
"""
TODO: Add module docstring
"""
from ipywidgets import DOMWidget
from traitlets import Unicode
from ._frontend import module_name, module_version
class ExampleWidget(DOMWidget):
"""TODO: Add docstring here
"""
_model_name = Unicode('ReportModel').tag(sync=True)
_model_module = Unicode(module_name).tag(sync=True)
_model_module_version = Unicode(module_version).tag(sync=True)
_view_name = Unicode('ReportView').tag(sync=True)
_view_module = Unicode(module_name).tag(sync=True)
_view_module_version = Unicode(module_version).tag(sync=True)
value = Unicode('Hello World').tag(sync=True)

Просмотреть файл

@ -0,0 +1,13 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft
# Distributed under the terms of the Modified BSD License.
def _jupyter_nbextension_paths():
return [{
'section': 'notebook',
'src': 'nbextension/static',
'dest': 'powerbi_widget',
'require': 'powerbi_widget/extension'
}]

Просмотреть файл

@ -0,0 +1,17 @@
// Entry point for the notebook bundle containing custom model definitions.
//
define(function() {
"use strict";
window['requirejs'].config({
map: {
'*': {
'powerbi-widget-client': 'nbextensions/powerbi_widget/index',
},
}
});
// Export the required load_ipython_extension function
return {
load_ipython_extension : function() {}
};
});

17
powerbi_widget/report.py Normal file
Просмотреть файл

@ -0,0 +1,17 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
"""
TODO: Add module docstring
"""
from ipywidgets import DOMWidget
from traitlets import Unicode
from ._frontend import module_name, module_version
class Report(DOMWidget):
"""TODO: Add docstring here
"""

Просмотреть файл

Просмотреть файл

@ -0,0 +1,54 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ipykernel.comm import Comm
from ipywidgets import Widget
class MockComm(Comm):
"""A mock Comm object.
Can be used to inspect calls to Comm's open/send/close methods.
"""
comm_id = 'a-b-c-d'
kernel = 'Truthy'
def __init__(self, *args, **kwargs):
self.log_open = []
self.log_send = []
self.log_close = []
super(MockComm, self).__init__(*args, **kwargs)
def open(self, *args, **kwargs):
self.log_open.append((args, kwargs))
def send(self, *args, **kwargs):
self.log_send.append((args, kwargs))
def close(self, *args, **kwargs):
self.log_close.append((args, kwargs))
_widget_attrs = {}
undefined = object()
@pytest.fixture
def mock_comm():
_widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
Widget._comm_default = lambda self: MockComm()
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
def raise_not_implemented(*args, **kwargs):
raise NotImplementedError()
Widget._ipython_display_ = raise_not_implemented
yield MockComm()
for attr, value in _widget_attrs.items():
if value is undefined:
delattr(Widget, attr)
else:
setattr(Widget, attr, value)

Просмотреть файл

@ -0,0 +1,14 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
import pytest
from ..example import ExampleWidget
def test_example_creation_blank():
w = ExampleWidget()
assert w.value == 'Hello World'

Просмотреть файл

@ -0,0 +1,15 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Microsoft.
# Distributed under the terms of the Modified BSD License.
def test_nbextension_path():
# Check that magic function can be imported from package root:
from powerbi_widget import _jupyter_nbextension_paths
# Ensure that it can be called without incident:
path = _jupyter_nbextension_paths()
# Some sanity checks:
assert len(path) == 1
assert isinstance(path[0], dict)

4
pytest.ini Normal file
Просмотреть файл

@ -0,0 +1,4 @@
[pytest]
testpaths = powerbi_widget/tests examples
norecursedirs = node_modules .ipynb_checkpoints
addopts = --nbval --current-env

9
readthedocs.yml Normal file
Просмотреть файл

@ -0,0 +1,9 @@
type: sphinx
python:
version: 3.5
pip_install: true
extra_requirements:
- examples
- docs
conda:
file: docs/environment.yml

6
setup.cfg Normal file
Просмотреть файл

@ -0,0 +1,6 @@
[bdist_wheel]
universal=1
[metadata]
description-file = README.md
license_file = LICENSE.txt

116
setup.py Normal file
Просмотреть файл

@ -0,0 +1,116 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import print_function
from glob import glob
from os.path import join as pjoin
from setupbase import (
create_cmdclass, install_npm, ensure_targets,
find_packages, combine_commands, ensure_python,
get_version, HERE
)
from setuptools import setup
# The name of the project
name = 'powerbi_widget'
# Ensure a valid python version
ensure_python('>=3.4')
# Get our version
version = get_version(pjoin(name, '_version.py'))
nb_path = pjoin(HERE, name, 'nbextension', 'static')
lab_path = pjoin(HERE, name, 'labextension')
# Representative files that should exist after a successful build
jstargets = [
pjoin(nb_path, 'index.js'),
pjoin(HERE, 'lib', 'plugin.js'),
]
package_data_spec = {
name: [
'nbextension/static/*.*js*',
'labextension/*.tgz'
]
}
data_files_spec = [
('share/jupyter/nbextensions/powerbi_widget',
nb_path, '*.js*'),
('share/jupyter/lab/extensions', lab_path, '*.tgz'),
('etc/jupyter/nbconfig/notebook.d' , HERE, 'powerbi_widget.json')
]
cmdclass = create_cmdclass('jsdeps', package_data_spec=package_data_spec,
data_files_spec=data_files_spec)
cmdclass['jsdeps'] = combine_commands(
install_npm(HERE, build_cmd='build:all'),
ensure_targets(jstargets),
)
setup_args = dict(
name = name,
description = 'A Custom Jupyter Widget Library',
version = version,
scripts = glob(pjoin('scripts', '*')),
cmdclass = cmdclass,
packages = find_packages(),
author = 'Microsoft',
author_email = '',
url = 'https://github.com/Microsoft/powerbi-jupyter',
license = 'BSD',
platforms = "Linux, Mac OS X, Windows",
keywords = ['Jupyter', 'Widgets', 'IPython'],
classifiers = [
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Framework :: Jupyter',
],
include_package_data = True,
install_requires = [
'ipywidgets>=7.0.0',
],
extras_require = {
'test': [
'pytest>=4.6',
'pytest-cov',
'nbval',
],
'examples': [
# Any requirements for the examples to run
],
'docs': [
'sphinx>=1.5',
'recommonmark',
'sphinx_rtd_theme',
'nbsphinx>=0.2.13,<0.4.0',
'jupyter_sphinx',
'nbsphinx-link',
'pytest_check_links',
'pypandoc',
],
},
entry_points = {
},
)
if __name__ == '__main__':
setup(**setup_args)

718
setupbase.py Normal file
Просмотреть файл

@ -0,0 +1,718 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
This file originates from the 'jupyter-packaging' package, and
contains a set of useful utilities for including npm packages
within a Python package.
"""
from collections import defaultdict
from os.path import join as pjoin
import io
import os
import functools
import pipes
import re
import shlex
import subprocess
import sys
# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
# update it when the contents of directories change.
if os.path.exists('MANIFEST'): os.remove('MANIFEST')
from distutils.cmd import Command
from distutils.command.build_py import build_py
from distutils.command.sdist import sdist
from distutils import log
from setuptools.command.develop import develop
from setuptools.command.bdist_egg import bdist_egg
try:
from wheel.bdist_wheel import bdist_wheel
except ImportError:
bdist_wheel = None
if sys.platform == 'win32':
from subprocess import list2cmdline
else:
def list2cmdline(cmd_list):
return ' '.join(map(pipes.quote, cmd_list))
__version__ = '0.2.0'
# ---------------------------------------------------------------------------
# Top Level Variables
# ---------------------------------------------------------------------------
HERE = os.path.abspath(os.path.dirname(__file__))
is_repo = os.path.exists(pjoin(HERE, '.git'))
node_modules = pjoin(HERE, 'node_modules')
SEPARATORS = os.sep if os.altsep is None else os.sep + os.altsep
npm_path = ':'.join([
pjoin(HERE, 'node_modules', '.bin'),
os.environ.get('PATH', os.defpath),
])
if "--skip-npm" in sys.argv:
print("Skipping npm install as requested.")
skip_npm = True
sys.argv.remove("--skip-npm")
else:
skip_npm = False
# ---------------------------------------------------------------------------
# Public Functions
# ---------------------------------------------------------------------------
def get_version(file, name='__version__'):
"""Get the version of the package from the given file by
executing it and extracting the given `name`.
"""
path = os.path.realpath(file)
version_ns = {}
with io.open(path, encoding="utf8") as f:
exec(f.read(), {}, version_ns)
return version_ns[name]
def ensure_python(specs):
"""Given a list of range specifiers for python, ensure compatibility.
"""
if not isinstance(specs, (list, tuple)):
specs = [specs]
v = sys.version_info
part = '%s.%s' % (v.major, v.minor)
for spec in specs:
if part == spec:
return
try:
if eval(part + spec):
return
except SyntaxError:
pass
raise ValueError('Python version %s unsupported' % part)
def find_packages(top=HERE):
"""
Find all of the packages.
"""
packages = []
for d, dirs, _ in os.walk(top, followlinks=True):
if os.path.exists(pjoin(d, '__init__.py')):
packages.append(os.path.relpath(d, top).replace(os.path.sep, '.'))
elif d != top:
# Do not look for packages in subfolders if current is not a package
dirs[:] = []
return packages
def update_package_data(distribution):
"""update build_py options to get package_data changes"""
build_py = distribution.get_command_obj('build_py')
build_py.finalize_options()
class bdist_egg_disabled(bdist_egg):
"""Disabled version of bdist_egg
Prevents setup.py install performing setuptools' default easy_install,
which it should never ever do.
"""
def run(self):
sys.exit("Aborting implicit building of eggs. Use `pip install .` "
" to install from source.")
def create_cmdclass(prerelease_cmd=None, package_data_spec=None,
data_files_spec=None):
"""Create a command class with the given optional prerelease class.
Parameters
----------
prerelease_cmd: (name, Command) tuple, optional
The command to run before releasing.
package_data_spec: dict, optional
A dictionary whose keys are the dotted package names and
whose values are a list of glob patterns.
data_files_spec: list, optional
A list of (path, dname, pattern) tuples where the path is the
`data_files` install path, dname is the source directory, and the
pattern is a glob pattern.
Notes
-----
We use specs so that we can find the files *after* the build
command has run.
The package data glob patterns should be relative paths from the package
folder containing the __init__.py file, which is given as the package
name.
e.g. `dict(foo=['./bar/*', './baz/**'])`
The data files directories should be absolute paths or relative paths
from the root directory of the repository. Data files are specified
differently from `package_data` because we need a separate path entry
for each nested folder in `data_files`, and this makes it easier to
parse.
e.g. `('share/foo/bar', 'pkgname/bizz, '*')`
"""
wrapped = [prerelease_cmd] if prerelease_cmd else []
if package_data_spec or data_files_spec:
wrapped.append('handle_files')
wrapper = functools.partial(_wrap_command, wrapped)
handle_files = _get_file_handler(package_data_spec, data_files_spec)
if 'bdist_egg' in sys.argv:
egg = wrapper(bdist_egg, strict=True)
else:
egg = bdist_egg_disabled
cmdclass = dict(
build_py=wrapper(build_py, strict=is_repo),
bdist_egg=egg,
sdist=wrapper(sdist, strict=True),
handle_files=handle_files,
)
if bdist_wheel:
cmdclass['bdist_wheel'] = wrapper(bdist_wheel, strict=True)
cmdclass['develop'] = wrapper(develop, strict=True)
return cmdclass
def command_for_func(func):
"""Create a command that calls the given function."""
class FuncCommand(BaseCommand):
def run(self):
func()
update_package_data(self.distribution)
return FuncCommand
def run(cmd, **kwargs):
"""Echo a command before running it. Defaults to repo as cwd"""
log.info('> ' + list2cmdline(cmd))
kwargs.setdefault('cwd', HERE)
kwargs.setdefault('shell', os.name == 'nt')
if not isinstance(cmd, (list, tuple)) and os.name != 'nt':
cmd = shlex.split(cmd)
cmd_path = which(cmd[0])
if not cmd_path:
sys.exit("Aborting. Could not find cmd (%s) in path. "
"If command is not expected to be in user's path, "
"use an absolute path." % cmd[0])
cmd[0] = cmd_path
return subprocess.check_call(cmd, **kwargs)
def is_stale(target, source):
"""Test whether the target file/directory is stale based on the source
file/directory.
"""
if not os.path.exists(target):
return True
target_mtime = recursive_mtime(target) or 0
return compare_recursive_mtime(source, cutoff=target_mtime)
class BaseCommand(Command):
"""Empty command because Command needs subclasses to override too much"""
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def get_inputs(self):
return []
def get_outputs(self):
return []
def combine_commands(*commands):
"""Return a Command that combines several commands."""
class CombinedCommand(Command):
user_options = []
def initialize_options(self):
self.commands = []
for C in commands:
self.commands.append(C(self.distribution))
for c in self.commands:
c.initialize_options()
def finalize_options(self):
for c in self.commands:
c.finalize_options()
def run(self):
for c in self.commands:
c.run()
return CombinedCommand
def compare_recursive_mtime(path, cutoff, newest=True):
"""Compare the newest/oldest mtime for all files in a directory.
Cutoff should be another mtime to be compared against. If an mtime that is
newer/older than the cutoff is found it will return True.
E.g. if newest=True, and a file in path is newer than the cutoff, it will
return True.
"""
if os.path.isfile(path):
mt = mtime(path)
if newest:
if mt > cutoff:
return True
elif mt < cutoff:
return True
for dirname, _, filenames in os.walk(path, topdown=False):
for filename in filenames:
mt = mtime(pjoin(dirname, filename))
if newest: # Put outside of loop?
if mt > cutoff:
return True
elif mt < cutoff:
return True
return False
def recursive_mtime(path, newest=True):
"""Gets the newest/oldest mtime for all files in a directory."""
if os.path.isfile(path):
return mtime(path)
current_extreme = None
for dirname, dirnames, filenames in os.walk(path, topdown=False):
for filename in filenames:
mt = mtime(pjoin(dirname, filename))
if newest: # Put outside of loop?
if mt >= (current_extreme or mt):
current_extreme = mt
elif mt <= (current_extreme or mt):
current_extreme = mt
return current_extreme
def mtime(path):
"""shorthand for mtime"""
return os.stat(path).st_mtime
def install_npm(path=None, build_dir=None, source_dir=None, build_cmd='build', force=False, npm=None):
"""Return a Command for managing an npm installation.
Note: The command is skipped if the `--skip-npm` flag is used.
Parameters
----------
path: str, optional
The base path of the node package. Defaults to the repo root.
build_dir: str, optional
The target build directory. If this and source_dir are given,
the JavaScript will only be build if necessary.
source_dir: str, optional
The source code directory.
build_cmd: str, optional
The npm command to build assets to the build_dir.
npm: str or list, optional.
The npm executable name, or a tuple of ['node', executable].
"""
class NPM(BaseCommand):
description = 'install package.json dependencies using npm'
def run(self):
if skip_npm:
log.info('Skipping npm-installation')
return
node_package = path or HERE
node_modules = pjoin(node_package, 'node_modules')
is_yarn = os.path.exists(pjoin(node_package, 'yarn.lock'))
npm_cmd = npm
if npm is None:
if is_yarn:
npm_cmd = ['yarn']
else:
npm_cmd = ['npm']
if not which(npm_cmd[0]):
log.error("`{0}` unavailable. If you're running this command "
"using sudo, make sure `{0}` is available to sudo"
.format(npm_cmd[0]))
return
if force or is_stale(node_modules, pjoin(node_package, 'package.json')):
log.info('Installing build dependencies with npm. This may '
'take a while...')
run(npm_cmd + ['install'], cwd=node_package)
if build_dir and source_dir and not force:
should_build = is_stale(build_dir, source_dir)
else:
should_build = True
if should_build:
run(npm_cmd + ['run', build_cmd], cwd=node_package)
return NPM
def ensure_targets(targets):
"""Return a Command that checks that certain files exist.
Raises a ValueError if any of the files are missing.
Note: The check is skipped if the `--skip-npm` flag is used.
"""
class TargetsCheck(BaseCommand):
def run(self):
if skip_npm:
log.info('Skipping target checks')
return
missing = [t for t in targets if not os.path.exists(t)]
if missing:
raise ValueError(('missing files: %s' % missing))
return TargetsCheck
# `shutils.which` function copied verbatim from the Python-3.3 source.
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
"""Given a command, mode, and a PATH string, return the path which
conforms to the given mode on the PATH, or None if there is no such
file.
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
of os.environ.get("PATH"), or can be overridden with a custom search
path.
"""
# Check that a given file can be accessed with the correct mode.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def _access_check(fn, mode):
return (os.path.exists(fn) and os.access(fn, mode) and
not os.path.isdir(fn))
# Short circuit. If we're given a full path which matches the mode
# and it exists, we're done here.
if _access_check(cmd, mode):
return cmd
path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep)
if sys.platform == "win32":
# The current directory takes precedence on Windows.
if os.curdir not in path:
path.insert(0, os.curdir)
# PATHEXT is necessary to check on Windows.
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
# See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe".
matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())]
# If it does match, only test that one, otherwise we have to try
# others.
files = [cmd] if matches else [cmd + ext.lower() for ext in pathext]
else:
# On other platforms you don't have things like PATHEXT to tell you
# what file suffixes are executable, so just pass on cmd as-is.
files = [cmd]
seen = set()
for dir in path:
dir = os.path.normcase(dir)
if dir not in seen:
seen.add(dir)
for thefile in files:
name = os.path.join(dir, thefile)
if _access_check(name, mode):
return name
return None
# ---------------------------------------------------------------------------
# Private Functions
# ---------------------------------------------------------------------------
def _wrap_command(cmds, cls, strict=True):
"""Wrap a setup command
Parameters
----------
cmds: list(str)
The names of the other commands to run prior to the command.
strict: boolean, optional
Wether to raise errors when a pre-command fails.
"""
class WrappedCommand(cls):
def run(self):
if not getattr(self, 'uninstall', None):
try:
[self.run_command(cmd) for cmd in cmds]
except Exception:
if strict:
raise
else:
pass
# update package data
update_package_data(self.distribution)
result = cls.run(self)
return result
return WrappedCommand
def _get_file_handler(package_data_spec, data_files_spec):
"""Get a package_data and data_files handler command.
"""
class FileHandler(BaseCommand):
def run(self):
package_data = self.distribution.package_data
package_spec = package_data_spec or dict()
for (key, patterns) in package_spec.items():
package_data[key] = _get_package_data(key, patterns)
self.distribution.data_files = _get_data_files(
data_files_spec, self.distribution.data_files
)
return FileHandler
def _glob_pjoin(*parts):
"""Join paths for glob processing"""
if parts[0] in ('.', ''):
parts = parts[1:]
return pjoin(*parts).replace(os.sep, '/')
def _get_data_files(data_specs, existing, top=HERE):
"""Expand data file specs into valid data files metadata.
Parameters
----------
data_specs: list of tuples
See [create_cmdclass] for description.
existing: list of tuples
The existing distrubution data_files metadata.
Returns
-------
A valid list of data_files items.
"""
# Extract the existing data files into a staging object.
file_data = defaultdict(list)
for (path, files) in existing or []:
file_data[path] = files
# Extract the files and assign them to the proper data
# files path.
for (path, dname, pattern) in data_specs or []:
if os.path.isabs(dname):
dname = os.path.relpath(dname, top)
dname = dname.replace(os.sep, '/')
offset = 0 if dname in ('.', '') else len(dname) + 1
files = _get_files(_glob_pjoin(dname, pattern), top=top)
for fname in files:
# Normalize the path.
root = os.path.dirname(fname)
full_path = _glob_pjoin(path, root[offset:])
print(dname, root, full_path, offset)
if full_path.endswith('/'):
full_path = full_path[:-1]
file_data[full_path].append(fname)
# Construct the data files spec.
data_files = []
for (path, files) in file_data.items():
data_files.append((path, files))
return data_files
def _get_files(file_patterns, top=HERE):
"""Expand file patterns to a list of paths.
Parameters
-----------
file_patterns: list or str
A list of glob patterns for the data file locations.
The globs can be recursive if they include a `**`.
They should be relative paths from the top directory or
absolute paths.
top: str
the directory to consider for data files
Note:
Files in `node_modules` are ignored.
"""
if not isinstance(file_patterns, (list, tuple)):
file_patterns = [file_patterns]
for i, p in enumerate(file_patterns):
if os.path.isabs(p):
file_patterns[i] = os.path.relpath(p, top)
matchers = [_compile_pattern(p) for p in file_patterns]
files = set()
for root, dirnames, filenames in os.walk(top):
# Don't recurse into node_modules
if 'node_modules' in dirnames:
dirnames.remove('node_modules')
for m in matchers:
for filename in filenames:
fn = os.path.relpath(_glob_pjoin(root, filename), top)
fn = fn.replace(os.sep, '/')
if m(fn):
files.add(fn.replace(os.sep, '/'))
return list(files)
def _get_package_data(root, file_patterns=None):
"""Expand file patterns to a list of `package_data` paths.
Parameters
-----------
root: str
The relative path to the package root from `HERE`.
file_patterns: list or str, optional
A list of glob patterns for the data file locations.
The globs can be recursive if they include a `**`.
They should be relative paths from the root or
absolute paths. If not given, all files will be used.
Note:
Files in `node_modules` are ignored.
"""
if file_patterns is None:
file_patterns = ['*']
return _get_files(file_patterns, _glob_pjoin(HERE, root))
def _compile_pattern(pat, ignore_case=True):
"""Translate and compile a glob pattern to a regular expression matcher."""
if isinstance(pat, bytes):
pat_str = pat.decode('ISO-8859-1')
res_str = _translate_glob(pat_str)
res = res_str.encode('ISO-8859-1')
else:
res = _translate_glob(pat)
flags = re.IGNORECASE if ignore_case else 0
return re.compile(res, flags=flags).match
def _iexplode_path(path):
"""Iterate over all the parts of a path.
Splits path recursively with os.path.split().
"""
(head, tail) = os.path.split(path)
if not head or (not tail and head == path):
if head:
yield head
if tail or not head:
yield tail
return
for p in _iexplode_path(head):
yield p
yield tail
def _translate_glob(pat):
"""Translate a glob PATTERN to a regular expression."""
translated_parts = []
for part in _iexplode_path(pat):
translated_parts.append(_translate_glob_part(part))
os_sep_class = '[%s]' % re.escape(SEPARATORS)
res = _join_translated(translated_parts, os_sep_class)
return '{res}\\Z(?ms)'.format(res=res)
def _join_translated(translated_parts, os_sep_class):
"""Join translated glob pattern parts.
This is different from a simple join, as care need to be taken
to allow ** to match ZERO or more directories.
"""
res = ''
for part in translated_parts[:-1]:
if part == '.*':
# drop separator, since it is optional
# (** matches ZERO or more dirs)
res += part
else:
res += part + os_sep_class
if translated_parts[-1] == '.*':
# Final part is **
res += '.+'
# Follow stdlib/git convention of matching all sub files/directories:
res += '({os_sep_class}?.*)?'.format(os_sep_class=os_sep_class)
else:
res += translated_parts[-1]
return res
def _translate_glob_part(pat):
"""Translate a glob PATTERN PART to a regular expression."""
# Code modified from Python 3 standard lib fnmatch:
if pat == '**':
return '.*'
i, n = 0, len(pat)
res = []
while i < n:
c = pat[i]
i = i + 1
if c == '*':
# Match anything but path separators:
res.append('[^%s]*' % SEPARATORS)
elif c == '?':
res.append('[^%s]?' % SEPARATORS)
elif c == '[':
j = i
if j < n and pat[j] == '!':
j = j + 1
if j < n and pat[j] == ']':
j = j + 1
while j < n and pat[j] != ']':
j = j + 1
if j >= n:
res.append('\\[')
else:
stuff = pat[i:j].replace('\\', '\\\\')
i = j + 1
if stuff[0] == '!':
stuff = '^' + stuff[1:]
elif stuff[0] == '^':
stuff = '\\' + stuff
res.append('[%s]' % stuff)
else:
res.append(re.escape(c))
return ''.join(res)

16
src/extension.ts Normal file
Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// Entry point for the notebook bundle containing custom model definitions.
//
// Setup notebook base URL
//
// Some static assets may be required by the custom widget javascript. The base
// url for the notebook is not known at build time and is therefore computed
// dynamically.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(window as any).__webpack_public_path__ =
document.querySelector('body')!.getAttribute('data-base-url') +
'nbextensions/powerbi_widget';
export * from './index';

5
src/index.ts Normal file
Просмотреть файл

@ -0,0 +1,5 @@
// Copyright (c) Microsoft
// Distributed under the terms of the Modified BSD License.
export * from './version';
export * from './widget';

42
src/plugin.ts Normal file
Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft
// Distributed under the terms of the Modified BSD License.
import { Application, IPlugin } from '@phosphor/application';
import { Widget } from '@phosphor/widgets';
import { IJupyterWidgetRegistry } from '@jupyter-widgets/base';
import * as widgetExports from './widget';
import { MODULE_NAME, MODULE_VERSION } from './version';
const EXTENSION_ID = 'powerbi-widget-client:plugin';
/**
* The example plugin.
*/
const examplePlugin: IPlugin<Application<Widget>, void> = ({
id: EXTENSION_ID,
requires: [IJupyterWidgetRegistry],
activate: activateWidgetExtension,
autoStart: true,
} as unknown) as IPlugin<Application<Widget>, void>;
// the "as unknown as ..." typecast above is solely to support JupyterLab 1
// and 2 in the same codebase and should be removed when we migrate to Lumino.
export default examplePlugin;
/**
* Activate the widget extension.
*/
function activateWidgetExtension(
app: Application<Widget>,
registry: IJupyterWidgetRegistry
): void {
registry.registerWidget({
name: MODULE_NAME,
version: MODULE_VERSION,
exports: widgetExports,
});
}

18
src/version.ts Normal file
Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft
// Distributed under the terms of the Modified BSD License.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const data = require('../package.json');
/**
* The _model_module_version/_view_module_version this package implements.
*
* The html widget manager assumes that this is the same as the npm package
* version number.
*/
export const MODULE_VERSION = data.version;
/*
* The current package name.
*/
export const MODULE_NAME = data.name;

53
src/widget.ts Normal file
Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft
// Distributed under the terms of the Modified BSD License.
import {
DOMWidgetModel,
DOMWidgetView,
ISerializers,
} from '@jupyter-widgets/base';
import { MODULE_NAME, MODULE_VERSION } from './version';
// Import the CSS
import '../css/widget.css';
export class ReportModel extends DOMWidgetModel {
defaults() {
return {
...super.defaults(),
_model_name: ReportModel.model_name,
_model_module: ReportModel.model_module,
_model_module_version: ReportModel.model_module_version,
_view_name: ReportModel.view_name,
_view_module: ReportModel.view_module,
_view_module_version: ReportModel.view_module_version,
value: 'Hello World',
};
}
static serializers: ISerializers = {
...DOMWidgetModel.serializers,
// Add any extra serializers here
};
static model_name = 'ReportModel';
static model_module = MODULE_NAME;
static model_module_version = MODULE_VERSION;
static view_name = 'ReportView'; // Set to null if no view
static view_module = MODULE_NAME; // Set to null if no view
static view_module_version = MODULE_VERSION;
}
export class ReportView extends DOMWidgetView {
render() {
this.el.classList.add('custom-widget');
this.value_changed();
this.model.on('change:value', this.value_changed, this);
}
value_changed() {
this.el.textContent = this.model.get('value');
}
}

58
test.ipynb Normal file
Просмотреть файл

@ -0,0 +1,58 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import powerbi_widget"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a03c468f7a384a6b9a30f8f7cc080ab7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"ExampleWidget()"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"powerbi_widget.ExampleWidget()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

46
tests/karma.conf.js Normal file
Просмотреть файл

@ -0,0 +1,46 @@
module.exports = function (config) {
config.set({
basePath: '..',
frameworks: ['mocha', 'karma-typescript'],
reporters: ['mocha', 'karma-typescript'],
client: {
mocha: {
timeout : 10000, // 10 seconds - upped from 2 seconds
retries: 3 // Allow for slow server on CI.
}
},
files: [
{ pattern: "tests/src/**/*.ts" },
{ pattern: "src/**/*.ts" },
],
exclude: [
"src/extension.ts",
],
preprocessors: {
'**/*.ts': ['karma-typescript']
},
browserNoActivityTimeout: 31000, // 31 seconds - upped from 10 seconds
port: 9876,
colors: true,
singleRun: true,
logLevel: config.LOG_INFO,
karmaTypescriptConfig: {
tsconfig: 'tests/tsconfig.json',
reports: {
"text-summary": "",
"html": "coverage",
"lcovonly": {
"directory": "coverage",
"filename": "coverage.lcov"
}
},
bundlerOptions: {
transforms: [
require("karma-typescript-es6-transform")()
]
}
}
});
};

38
tests/src/index.spec.ts Normal file
Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import expect = require('expect.js');
import {
// Add any needed widget imports here (or from controls)
} from '@jupyter-widgets/base';
import {
createTestModel
} from './utils.spec';
import {
ReportModel, ReportView
} from '../../src/'
describe('Example', () => {
describe('ReportModel', () => {
it('should be createable', () => {
let model = createTestModel(ReportModel);
expect(model).to.be.an(ReportModel);
expect(model.get('value')).to.be('Hello World');
});
it('should be createable with a value', () => {
let state = { value: 'Foo Bar!' }
let model = createTestModel(ReportModel, state);
expect(model).to.be.an(ReportModel);
expect(model.get('value')).to.be('Foo Bar!');
});
});
});

113
tests/src/utils.spec.ts Normal file
Просмотреть файл

@ -0,0 +1,113 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import * as widgets from '@jupyter-widgets/base';
import * as services from '@jupyterlab/services';
import * as Backbone from 'backbone';
let numComms = 0;
export
class MockComm {
target_name = 'dummy';
constructor() {
this.comm_id = `mock-comm-id-${numComms}`;
numComms += 1;
}
on_close(fn: Function | null) {
this._on_close = fn;
}
on_msg(fn: Function | null) {
this._on_msg = fn;
}
_process_msg(msg: services.KernelMessage.ICommMsgMsg) {
if (this._on_msg) {
return this._on_msg(msg);
} else {
return Promise.resolve();
}
}
close(): string {
if (this._on_close) {
this._on_close();
}
return 'dummy';
}
send(): string {
return 'dummy';
}
open(): string {
return 'dummy';
}
comm_id: string;
_on_msg: Function | null = null;
_on_close: Function | null = null;
}
export
class DummyManager extends widgets.ManagerBase<HTMLElement> {
constructor() {
super();
this.el = window.document.createElement('div');
}
display_view(msg: services.KernelMessage.IMessage, view: Backbone.View<Backbone.Model>, options: any) {
// TODO: make this a spy
// TODO: return an html element
return Promise.resolve(view).then(view => {
this.el.appendChild(view.el);
view.on('remove', () => console.log('view removed', view));
return view.el;
});
}
protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise<any> {
if (moduleName === '@jupyter-widgets/base') {
if ((widgets as any)[className]) {
return Promise.resolve((widgets as any)[className]);
} else {
return Promise.reject(`Cannot find class ${className}`)
}
} else if (moduleName === 'jupyter-datawidgets') {
if (this.testClasses[className]) {
return Promise.resolve(this.testClasses[className]);
} else {
return Promise.reject(`Cannot find class ${className}`)
}
} else {
return Promise.reject(`Cannot find module ${moduleName}`);
}
}
_get_comm_info() {
return Promise.resolve({});
}
_create_comm() {
return Promise.resolve(new MockComm());
}
el: HTMLElement;
testClasses: { [key: string]: any } = {};
}
export
interface Constructor<T> {
new (attributes?: any, options?: any): T;
}
export
function createTestModel<T extends widgets.WidgetModel>(constructor: Constructor<T>, attributes?: any): T {
let id = widgets.uuid();
let widget_manager = new DummyManager();
let modelOptions = {
widget_manager: widget_manager,
model_id: id,
}
return new constructor(attributes, modelOptions);
}

19
tests/tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,19 @@
{
"compilerOptions": {
"declaration": true,
"noImplicitAny": true,
"lib": ["dom", "es5", "es2015.promise", "es2015.iterable"],
"noEmitOnError": true,
"strictNullChecks": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "ES5",
"outDir": "build",
"skipLibCheck": true,
"sourceMap": true
},
"include": [
"src/*.ts",
"../src/**/*.ts"
]
}

23
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,23 @@
{
"compilerOptions": {
"declaration": true,
"esModuleInterop":true,
"lib": ["es2015", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noUnusedLocals": true,
"outDir": "lib",
"resolveJsonModule": true,
"rootDir": "src",
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"strictPropertyInitialization": false,
"target": "es2015"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
]
}

88
webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,88 @@
const path = require('path');
const version = require('./package.json').version;
// Custom webpack rules
const rules = [
{ test: /\.ts$/, loader: 'ts-loader' },
{ test: /\.js$/, loader: 'source-map-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader']}
];
// Packages that shouldn't be bundled but loaded at runtime
const externals = ['@jupyter-widgets/base'];
const resolve = {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".js"]
};
module.exports = [
/**
* Notebook extension
*
* This bundle only contains the part of the JavaScript that is run on load of
* the notebook.
*/
{
entry: './src/extension.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'powerbi_widget', 'nbextension', 'static'),
libraryTarget: 'amd'
},
module: {
rules: rules
},
devtool: 'source-map',
externals,
resolve,
},
/**
* Embeddable powerbi-widget-client bundle
*
* This bundle is almost identical to the notebook extension bundle. The only
* difference is in the configuration of the webpack public path for the
* static assets.
*
* The target bundle is always `dist/index.js`, which is the path required by
* the custom widget embedder.
*/
{
entry: './src/index.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'amd',
library: "powerbi-widget-client",
publicPath: 'https://unpkg.com/powerbi-widget-client@' + version + '/dist/'
},
devtool: 'source-map',
module: {
rules: rules
},
externals,
resolve,
},
/**
* Documentation widget bundle
*
* This bundle is used to embed widgets in the package documentation.
*/
{
entry: './src/index.ts',
output: {
filename: 'embed-bundle.js',
path: path.resolve(__dirname, 'docs', 'source', '_static'),
library: "powerbi-widget-client",
libraryTarget: 'amd'
},
module: {
rules: rules
},
devtool: 'source-map',
externals,
resolve,
}
];