Skip to content

Commit

Permalink
Merge branch 'main' into import-risk-excel
Browse files Browse the repository at this point in the history
  • Loading branch information
somehowchris authored Jun 11, 2024
2 parents b640b84 + 8a0b50a commit 9356cf2
Show file tree
Hide file tree
Showing 39 changed files with 2,110 additions and 312 deletions.
29 changes: 29 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
*$py.class
*.egg
*.egg-info
*.pyc
*.sqlite
*.sublime-project
*.sublime-workspace
*~
.*.sw?
.coverage
.DS_Store
.mypy_cache/
.pytest_cache/
.sw?
.tox/
build/
coverage
coverage.xml
Data.fs*
data/
development*.ini
dist/
venv/
htmlcov/
nosetests.xml
production.ini
pyvenv.cfg
test
tmp/
18 changes: 12 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
FROM python:3.12.2-alpine3.19
FROM python:3.12-slim

# Set the working directory
WORKDIR /app

# Install PostgreSQL development packages required for psycopg
RUN apk add --no-cache postgresql-dev gcc python3-dev musl-dev
# Install PostgreSQL development packages and other dependencies required for psycopg
RUN apt-get update && \
apt-get install -y --no-install-recommends \
postgresql-server-dev-all \
gcc \
python3-dev \
build-essential \
linux-headers-amd64 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT [ "pserve" ]
RUN python -m pip install --no-cache-dir -r requirements.txt
3 changes: 3 additions & 0 deletions development.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pyramid.available_languages =

sqlalchemy.url = sqlite:///%(here)s/riskmatrix.sqlite

openai_api_key=
anthropic_api_key=

session.type = file
session.data_dir = %(here)s/data/sessions/data
session.lock_dir = %(here)s/data/sessions/lock
Expand Down
7 changes: 6 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ zope.event==5.0
zope.interface==6.1
zope.schema==7.0.1
zope.sqlalchemy==3.1
psycopg2-binary==2.9.9
numpy==1.26.3
openai==1.12.0
plotly==5.18.0
anthropic
redis[hiredis]==5.0.3
uwsgi==2.0.25.1
psycopg2-binary==2.9.9

-e .
44 changes: 43 additions & 1 deletion src/riskmatrix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
from pyramid.config import Configurator
from pyramid_beaker import session_factory_from_settings
from typing import Any
from email.headerregistry import Address
from pyramid.settings import asbool
from .mail import PostmarkMailer

from riskmatrix.flash import MessageQueue
from riskmatrix.i18n import LocaleNegotiator
from riskmatrix.layouts.steps import show_steps
from riskmatrix.route_factories import root_factory
from riskmatrix.security import authenticated_user
from riskmatrix.security_policy import SessionSecurityPolicy
from openai import OpenAI
from anthropic import Anthropic


from typing import TYPE_CHECKING
Expand All @@ -22,6 +27,19 @@
def includeme(config: Configurator) -> None:
settings = config.registry.settings

default_sender = settings.get(
'email.default_sender',
'[email protected]'
)
token = settings.get('mail.postmark_token', '')
stream = settings.get('mail.postmark_stream', 'development')
blackhole = asbool(settings.get('mail.postmark_blackhole', False))
config.registry.registerUtility(PostmarkMailer(
Address(addr_spec=default_sender),
token,
stream,
blackhole=blackhole
))
config.include('pyramid_beaker')
config.include('pyramid_chameleon')
config.include('pyramid_layout')
Expand Down Expand Up @@ -65,11 +83,35 @@ def main(
environment=sentry_environment,
integrations=[PyramidIntegration(), SqlalchemyIntegration()],
traces_sample_rate=1.0,
profiles_sample_rate=0.25,
profiles_sample_rate=1.0,
enable_tracing=True,
send_default_pii=True
)
print("configured sentry")
print(sentry_dsn)

with Configurator(settings=settings, root_factory=root_factory) as config:
includeme(config)

if openai_apikey := settings.get('openai_api_key'):

openai_client = OpenAI(
api_key=openai_apikey
)
config.add_request_method(
lambda r: openai_client,
'openai',
reify=True
)
if anthropic_apikey := settings.get('anthropic_api_key'):
anthropic_client = Anthropic(
api_key=anthropic_apikey
)
config.add_request_method(
lambda r: anthropic_client,
'anthropic',
reify=True
)

app = config.make_wsgi_app()
return Fanstatic(app, versioning=True)
1 change: 0 additions & 1 deletion src/riskmatrix/data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def cell(self, data: Any) -> str:
params = {}
if 'class_name' in self.options:
params['class'] = self.options['class_name']

if callable(self.sort_key):
params['data_order'] = self.sort_key(data)
return f'<td {html_params(**params)}>{self.format_data(data)}</td>'
Expand Down
5 changes: 4 additions & 1 deletion src/riskmatrix/layouts/layout.pt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<script type="text/javascript" src="${layout.static_url('riskmatrix:static/js/bundle.min.js')}"></script>
<script type="text/javascript" src="${layout.static_url('riskmatrix:static/js/sentry.js')}"></script>
</tal:block>
<script type="text/javascript" src="${layout.static_url('riskmatrix:static/js/plotly.min.js')}"></script>
<script type="text/javascript" src="${layout.static_url('riskmatrix:static/js/marked.min.js')}"></script>

<title>RiskMatrix<tal:b tal:condition="exists:title"> — ${title}</tal:b></title>

</head>
Expand Down Expand Up @@ -58,7 +61,7 @@
</h2>
<!-- Text -->
<p class="text-white-60">
We need to come up <br>with a slogan
Lean Risk Management
</p>

</div>
Expand Down
13 changes: 7 additions & 6 deletions src/riskmatrix/layouts/navbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,11 @@ def __html__(self) -> str:
def navbar(context: object, request: 'IRequest') -> 'RenderData':
return {
'entries': [
NavbarEntry(
request,
_('Organization'),
request.route_url('organization')
),
NavbarEntry(
request,
_('Risk Catalog'),
request.route_url('risk_catalog')
request.route_url('risk_catalog'),
lambda request, url: request.path_url.startswith(request.route_url('risk_catalog'))
),
NavbarEntry(
request,
Expand All @@ -84,5 +80,10 @@ def navbar(context: object, request: 'IRequest') -> 'RenderData':
request.route_url('assessment'),
lambda request, url: request.show_steps,
),
NavbarEntry(
request,
_('Organization'),
request.route_url('organization')
),
]
}
15 changes: 15 additions & 0 deletions src/riskmatrix/mail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .exceptions import InactiveRecipient
from .exceptions import MailConnectionError
from .exceptions import MailError
from .interfaces import IMailer
from .mailer import PostmarkMailer
from .types import MailState

__all__ = (
'IMailer',
'InactiveRecipient',
'MailConnectionError',
'MailError',
'MailState',
'PostmarkMailer',
)
10 changes: 10 additions & 0 deletions src/riskmatrix/mail/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class MailError(Exception):
pass


class MailConnectionError(MailError, ConnectionError):
pass


class InactiveRecipient(MailError):
pass
91 changes: 91 additions & 0 deletions src/riskmatrix/mail/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from typing import Any, TYPE_CHECKING
from zope.interface import Interface

if TYPE_CHECKING:
from collections.abc import Sequence
from email.headerregistry import Address
from .types import MailState
from .types import MailParams
from .types import TemplateMailParams
from ..certificate.interfaces import ITemplate
from ..models import Organization
from ..types import JSONObject
MailID = Any


class IMailer(Interface): # pragma: no cover

# NOTE: We would like to say that kwargs is OptionalMailParams
# however there is no way in mypy to express that yet.
def send(sender: 'Address | None',
receivers: 'Address | Sequence[Address]',
subject: str,
content: str,
**kwargs: Any) -> 'MailID':
"""
Send a single email.
Returns a message uuid.
"""
pass

def bulk_send(mails: list['MailParams']
) -> list['MailID | MailState']:
"""
Send multiple emails. "mails" is a list of dicts containing
the arguments to an individual send call.
Returns a list of message uuids and their success/failure states
in the same order as the sending list.
"""
pass

# NOTE: We would like to say that kwargs is OptionalTemplateMailParams
# however there is no way in mypy to express that yet.
def send_template(sender: 'Address | None',
receivers: 'Address | Sequence[Address]',
template: str,
data: 'JSONObject',
**kwargs: Any) -> 'MailID':
"""
Send a single email using a template using its id/name.
"data" contains the template specific data.
Returns a message uuid.
"""
pass

def bulk_send_template(mails: list['TemplateMailParams'],
default_template: str | None = None,
) -> list['MailID | MailState']:
"""
Send multiple template emails using the same template.
Returns a list of message uuids. If a message failed to be sent
the uuid will be replaced by a MailState value.
"""
pass

def template_exists(alias: str) -> bool:
"""
Returns whether a template by the given alias exists.
"""
pass

def create_or_update_template(
template: 'ITemplate',
organization: 'Organization | None' = None,
) -> list[str]:
"""
Creates or updates a mailer template based on a certificate template.
Returns a list of errors. If the list is empty, it was successful.
"""
pass

def delete_template(template: 'ITemplate') -> list[str]:
"""
Deletes a mailer template based on a certificate template.
Returns a list of errors. If the list is empty, it was successful.
"""
Loading

0 comments on commit 9356cf2

Please sign in to comment.