Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Formatting

on:
push:
branches: [main]
pull_request:
branches: [main]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
merge_group:

permissions:
contents: read

jobs:
black:
runs-on: ubuntu-latest
concurrency:
group: lint-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python -
echo "$HOME/.poetry/bin" >> $GITHUB_PATH
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.9"
cache: poetry
- name: Install dependencies
run: |
poetry install --with dev
- name: Run black
uses: psf/black@stable
with:
options: "--check"
src: "."
version: "23.3.0" # Black version to use for formatting, since yearly they change the style, this keeps it stable
23 changes: 13 additions & 10 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

name: Tests

on:
Expand Down Expand Up @@ -68,35 +67,39 @@ jobs:

tests:
runs-on: ubuntu-latest
strategy:
matrix:
# Minimum supported; Maybe add in some newer version to test things won't break if used on newer versions of python?
python_version: ["3.9"] #, "3.x"]
concurrency:
group: tests-${{ github.ref }}-${{ matrix.python_version }}
group: tests-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python -
echo "$HOME/.poetry/bin" >> $GITHUB_PATH
- name: Set up Python ${{ matrix.python_version }}
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python_version }}
python-version: "3.9"
cache: poetry
- name: Install dependencies
run: |
poetry install --with dev
- name: Run tests
run: |
poetry run pytest tests
poetry run pytest --cov-report=xml:coverage.xml -rFxX tests/
- name: Upload generated files
if: failure()
uses: actions/upload-artifact@v3
with:
name: test-generated-files-${{ github.event_name }}-${{ matrix.python_version }}
name: test-generated-files-${{ github.event_name }}
path: tests/test_question_templates/question_generated_outputs/
if-no-files-found: ignore
retention-days: 5
# - name: Upload coverage report to codecov
# if: always()
# uses: codecov/codecov-action@v3
# with:
# # Needed for private repos, publi repos don't, but it is a good
# # fallback for if codecov fails to identify it properly as public
# # token: ${{ secrets.CODECOV_TOKEN }}
# files: ./coverage1.xml
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 'refs/tags/23.3.0:refs/tags/23.3.0'
hooks:
- id: black
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Python](https://img.shields.io/badge/python-3.9-blue)]()
[![codecov](https://codecov.io/gh/open-resources/problem_bank_scripts/branch/main/graph/badge.svg)](https://codecov.io/gh/open-resources/problem_bank_scripts)
[![Documentation Status](https://readthedocs.org/projects/problem_bank_scripts/badge/?version=latest)](https://problem_bank_scripts.readthedocs.io/en/latest/?badge=latest)
[![Tests](https://github.com/open-resources/problem_bank_scripts/actions/workflows/tests.yml/badge.svg)](https://github.com/open-resources/problem_bank_scripts/actions/workflows/tests.yml)


## Installation
Expand Down
6 changes: 3 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

# -- Project information -----------------------------------------------------

project = u"Problem Bank Scripts"
copyright = u"2021, Open Problem Bank Team"
author = u"Open Problem Bank Team"
project = "Problem Bank Scripts"
copyright = "2021, Open Problem Bank Team"
author = "Open Problem Bank Team"

# -- General configuration ---------------------------------------------------

Expand Down
185 changes: 181 additions & 4 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mdformat = "^0.7.14"
sympy = "^1.8"
problem-bank-helpers = "^0.1.14"

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
Sphinx = "^4.4"
nbsphinx = "^0.8.5"
ipykernel = "^5.5.5"
Expand All @@ -30,10 +30,18 @@ sphinxcontrib-napoleon = "^0.7"
pytest = "^6.2.4"
myst-nb = "^0.15"
myst-parser = "^0.17"
pytest-cov = "^4.1.0"
black = {extras = ["jupyter"], version = "^23.3.0"}

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
filterwarnings = ["ignore::DeprecationWarning"]
addopts = "--cov=src/problem_bank_scripts --cov-report=term --cov-branch"

[tool.black]
required_version = "23" # Black version to use for formatting, since yearly they change the style
force-exclude = "/tests/test_question_templates"
line-length = 100
91 changes: 61 additions & 30 deletions src/problem_bank_scripts/prairielearn.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# This file has been copied directly from the PL repo: https://github.com/PrairieLearn/PrairieLearn/blob/master/lib/python_helper_sympy.py

import sympy

# import ast
# import sys

# import lxml.html
# import html
# import to_precision
import numpy as np

# import uuid
# import sympy
import pandas

# from python_helper_sympy import convert_string_to_sympy
# from python_helper_sympy import sympy_to_json
# from python_helper_sympy import json_to_sympy
Expand All @@ -22,45 +25,47 @@
# import os
import collections


# Create a new instance of this class to access the member dictionaries. This
# is to avoid accidentally modifying these dictionaries.
class _Constants:
def __init__(self):
self.helpers = {
'_Integer': sympy.Integer,
"_Integer": sympy.Integer,
}
self.variables = {
'pi': sympy.pi,
'e': sympy.E,
"pi": sympy.pi,
"e": sympy.E,
}
self.hidden_variables = {
'_Exp1': sympy.E,
"_Exp1": sympy.E,
}
self.complex_variables = {
'i': sympy.I,
'j': sympy.I,
"i": sympy.I,
"j": sympy.I,
}
self.hidden_complex_variables = {
'_ImaginaryUnit': sympy.I,
"_ImaginaryUnit": sympy.I,
}
self.functions = {
# These are shown to the student
'cos': sympy.cos,
'sin': sympy.sin,
'tan': sympy.tan,
'arccos': sympy.acos,
'arcsin': sympy.asin,
'arctan': sympy.atan,
'acos': sympy.acos,
'asin': sympy.asin,
'atan': sympy.atan,
'arctan2': sympy.atan2,
'atan2': sympy.atan2,
'exp': sympy.exp,
'log': sympy.log,
'sqrt': sympy.sqrt,
"cos": sympy.cos,
"sin": sympy.sin,
"tan": sympy.tan,
"arccos": sympy.acos,
"arcsin": sympy.asin,
"arctan": sympy.atan,
"acos": sympy.acos,
"asin": sympy.asin,
"atan": sympy.atan,
"arctan2": sympy.atan2,
"atan2": sympy.atan2,
"exp": sympy.exp,
"log": sympy.log,
"sqrt": sympy.sqrt,
}


# # Safe evaluation of user input to convert from string to sympy expression.
# #
# # Adapted from:
Expand Down Expand Up @@ -276,6 +281,7 @@ def __init__(self):
# w_right = min(ind+w, len(s)) - ind
# return s[ind-w_left:ind+w_right] + '\n' + ' '*w_left + '^' + ' '*w_right


def sympy_to_json(a, allow_complex=True):
const = _Constants()

Expand All @@ -289,14 +295,22 @@ def sympy_to_json(a, allow_complex=True):
for k in reserved.keys():
for v in variables:
if k == v:
raise ValueError('sympy expression has a variable with a reserved name: {:s}'.format(k))
raise ValueError(
"sympy expression has a variable with a reserved name: {:s}".format(k)
)

# Apply substitutions for hidden variables
a = a.subs([(const.hidden_variables[key], key) for key in const.hidden_variables.keys()])
if allow_complex:
a = a.subs([(const.hidden_complex_variables[key], key) for key in const.hidden_complex_variables.keys()])
a = a.subs(
[
(const.hidden_complex_variables[key], key)
for key in const.hidden_complex_variables.keys()
]
)

return {"_type": "sympy", "_value": str(a), "_variables": variables}

return {'_type': 'sympy', '_value': str(a), '_variables': variables}

# def json_to_sympy(a, allow_complex=True):
# if not '_type' in a:
Expand All @@ -310,6 +324,7 @@ def sympy_to_json(a, allow_complex=True):

# return convert_string_to_sympy(a['_value'], a['_variables'], allow_hidden=True, allow_complex=allow_complex)


# Added to_json() from this file: https://github.com/PrairieLearn/PrairieLearn/blob/master/question-servers/freeformPythonLib/prairielearn.py
def to_json(v):
"""to_json(v)
Expand All @@ -328,12 +343,16 @@ def to_json(v):
returned without change.
"""
if np.isscalar(v) and np.iscomplexobj(v):
return {'_type': 'complex', '_value': {'real': v.real, 'imag': v.imag}}
return {"_type": "complex", "_value": {"real": v.real, "imag": v.imag}}
elif isinstance(v, np.ndarray):
if np.isrealobj(v):
return {'_type': 'ndarray', '_value': v.tolist(), '_dtype': str(v.dtype)}
return {"_type": "ndarray", "_value": v.tolist(), "_dtype": str(v.dtype)}
elif np.iscomplexobj(v):
return {'_type': 'complex_ndarray', '_value': {'real': v.real.tolist(), 'imag': v.imag.tolist()}, '_dtype': str(v.dtype)}
return {
"_type": "complex_ndarray",
"_value": {"real": v.real.tolist(), "imag": v.imag.tolist()},
"_dtype": str(v.dtype),
}
elif isinstance(v, sympy.Expr):
return sympy_to_json(v)
elif isinstance(v, sympy.Matrix) or isinstance(v, sympy.ImmutableMatrix):
Expand All @@ -345,8 +364,20 @@ def to_json(v):
for j in range(0, num_cols):
row.append(str(v[i, j]))
M.append(row)
return {'_type': 'sympy_matrix', '_value': M, '_variables': s, '_shape': [num_rows, num_cols]}
return {
"_type": "sympy_matrix",
"_value": M,
"_variables": s,
"_shape": [num_rows, num_cols],
}
elif isinstance(v, pandas.DataFrame):
return {'_type': 'dataframe', '_value': {'index': list(v.index), 'columns': list(v.columns), 'data': v.values.tolist()}}
return {
"_type": "dataframe",
"_value": {
"index": list(v.index),
"columns": list(v.columns),
"data": v.values.tolist(),
},
}
else:
return v
return v
Loading