mirror of
https://github.com/bec-project/bec_atlas.git
synced 2025-07-13 22:51:49 +02:00
feat(backend): basic login and scylladb setup
This commit is contained in:
180
.gitignore
vendored
Normal file
180
.gitignore
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
**/*_venv
|
||||
**/.idea
|
||||
*.log
|
||||
**/__pycache__
|
||||
**/.DS_Store
|
||||
**/out
|
||||
**/.vscode
|
||||
**/.pytest_cache
|
||||
**/*.egg*
|
||||
|
||||
# recovery_config files
|
||||
recovery_config_*
|
||||
|
||||
# file writer data
|
||||
**.h5
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# 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/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/**/_build/
|
||||
docs/**/autodoc/
|
||||
docs/**/_autosummary/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
**.prof
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
17
.gitlab/issue_templates/bug_report_template.md
Normal file
17
.gitlab/issue_templates/bug_report_template.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Bug report
|
||||
|
||||
## Summary
|
||||
|
||||
[Provide a brief description of the bug.]
|
||||
|
||||
## Expected Behavior vs Actual Behavior
|
||||
|
||||
[Describe what you expected to happen and what actually happened.]
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
[Outline the steps that lead to the bug's occurrence. Be specific and provide a clear sequence of actions.]
|
||||
|
||||
## Related Issues
|
||||
|
||||
[Paste links to any related issues or feature requests.]
|
27
.gitlab/issue_templates/documentation_update_template.md
Normal file
27
.gitlab/issue_templates/documentation_update_template.md
Normal file
@ -0,0 +1,27 @@
|
||||
## Documentation Section
|
||||
|
||||
[Specify the section or page of the documentation that needs updating]
|
||||
|
||||
## Current Information
|
||||
|
||||
[Provide the current information in the documentation that needs to be updated]
|
||||
|
||||
## Proposed Update
|
||||
|
||||
[Describe the proposed update or correction. Be specific about the changes that need to be made]
|
||||
|
||||
## Reason for Update
|
||||
|
||||
[Explain the reason for the documentation update. Include any recent changes, new features, or corrections that necessitate the update]
|
||||
|
||||
## Additional Context
|
||||
|
||||
[Include any additional context or information that can help the documentation team understand the update better]
|
||||
|
||||
## Attachments
|
||||
|
||||
[Attach any files, screenshots, or references that can assist in making the documentation update]
|
||||
|
||||
## Priority
|
||||
|
||||
[Assign a priority level to the documentation update based on its urgency. Use a scale such as Low, Medium, High]
|
40
.gitlab/issue_templates/feature_request_template.md
Normal file
40
.gitlab/issue_templates/feature_request_template.md
Normal file
@ -0,0 +1,40 @@
|
||||
## Feature Summary
|
||||
|
||||
[Provide a brief and clear summary of the new feature you are requesting]
|
||||
|
||||
## Problem Description
|
||||
|
||||
[Explain the problem or need that this feature aims to address. Be specific about the issues or gaps in the current functionality]
|
||||
|
||||
## Use Case
|
||||
|
||||
[Describe a real-world scenario or use case where this feature would be beneficial. Explain how it would improve the user experience or workflow]
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
[If you have a specific solution in mind, describe it here. Explain how it would work and how it would address the problem described above]
|
||||
|
||||
## Benefits
|
||||
|
||||
[Explain the benefits and advantages of implementing this feature. Highlight how it adds value to the product or improves user satisfaction]
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
[If you've considered alternative solutions or workarounds, mention them here. Explain why the proposed feature is the preferred option]
|
||||
|
||||
## Impact on Existing Functionality
|
||||
|
||||
[Discuss how the new feature might impact or interact with existing features. Address any potential conflicts or dependencies]
|
||||
|
||||
## Priority
|
||||
|
||||
[Assign a priority level to the feature request based on its importance. Use a scale such as Low, Medium, High]
|
||||
|
||||
## Attachments
|
||||
|
||||
[Include any relevant attachments, such as sketches, diagrams, or references that can help the development team understand your feature request better]
|
||||
|
||||
## Additional Information
|
||||
|
||||
[Provide any additional information that might be relevant to the feature request, such as user feedback, market trends, or similar features in other products]
|
||||
|
28
.gitlab/merge_request_templates/default.md
Normal file
28
.gitlab/merge_request_templates/default.md
Normal file
@ -0,0 +1,28 @@
|
||||
## Description
|
||||
|
||||
[Provide a brief description of the changes introduced by this merge request.]
|
||||
|
||||
## Related Issues
|
||||
|
||||
[Cite any related issues or feature requests that are addressed or resolved by this merge request. Use the gitlab syntax for linking issues, for example, `fixes #123` or `closes #123`.]
|
||||
|
||||
## Type of Change
|
||||
|
||||
- Change 1
|
||||
- Change 2
|
||||
|
||||
## Potential side effects
|
||||
|
||||
[Describe any potential side effects or risks of merging this MR.]
|
||||
|
||||
## Screenshots / GIFs (if applicable)
|
||||
|
||||
[Include any relevant screenshots or GIFs to showcase the changes made.]
|
||||
|
||||
## Additional Comments
|
||||
|
||||
[Add any additional comments or information that may be helpful for reviewers.]
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Documentation is up-to-date.
|
||||
|
581
.pylintrc
Normal file
581
.pylintrc
Normal file
@ -0,0 +1,581 @@
|
||||
[MASTER]
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code.
|
||||
extension-pkg-allow-list=
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
||||
# for backward compatibility.)
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Return non-zero exit code if any of these messages/categories are detected,
|
||||
# even if score is above --fail-under value. Syntax same as enable. Messages
|
||||
# specified are enabled, while categories only check already-enabled messages.
|
||||
fail-on=
|
||||
|
||||
# Specify a score threshold to be exceeded before program exits with error.
|
||||
fail-under=8.0
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=CVS
|
||||
|
||||
# Add files or directories matching the regex patterns to the ignore-list. The
|
||||
# regex matches against paths and can be in Posix or Windows format.
|
||||
ignore-paths=
|
||||
|
||||
# Files or directories matching the regex patterns are skipped. The regex
|
||||
# matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
||||
# number of processors available to use.
|
||||
jobs=1
|
||||
|
||||
# Control the amount of potential inferred values when inferring a single
|
||||
# object. This can help the performance when dealing with large functions or
|
||||
# complex, nested conditions.
|
||||
limit-inference-results=100
|
||||
|
||||
# List of plugins (as comma separated values of python module names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Minimum Python version to use for version dependent checks. Will default to
|
||||
# the version used to run pylint.
|
||||
py-version=3.10
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages.
|
||||
suggestion-mode=yes
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
|
||||
confidence=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once). You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||
# --disable=W".
|
||||
disable=missing-module-docstring,
|
||||
missing-class-docstring,
|
||||
import-error,
|
||||
no-name-in-module,
|
||||
raw-checker-failed,
|
||||
bad-inline-option,
|
||||
locally-disabled,
|
||||
file-ignored,
|
||||
suppressed-message,
|
||||
deprecated-pragma,
|
||||
use-symbolic-message-instead,
|
||||
unused-wildcard-import,
|
||||
logging-fstring-interpolation,
|
||||
line-too-long,
|
||||
too-many-instance-attributes,
|
||||
wrong-import-order
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Python expression which should return a score less than or equal to 10. You
|
||||
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
|
||||
# which contain the number of messages in each category, as well as 'statement'
|
||||
# which is the total number of statements analyzed. This score is used by the
|
||||
# global evaluation report (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details.
|
||||
#msg-template=
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json
|
||||
# and msvs (visual studio). You can also give a reporter class, e.g.
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages.
|
||||
reports=no
|
||||
|
||||
# Activate the evaluation score.
|
||||
score=yes
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=sys.exit,argparse.parse_error
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# The type of string formatting that logging methods do. `old` means using %
|
||||
# formatting, `new` is for `{}` formatting.
|
||||
logging-format-style=old
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format.
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes.
|
||||
max-spelling-suggestions=4
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it work,
|
||||
# install the 'python-enchant' package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should be considered directives if they
|
||||
# appear and the beginning of a comment and should not be checked.
|
||||
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains the private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to the private dictionary (see the
|
||||
# --spelling-private-dict-file option) instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
|
||||
# Regular expression of note tags to take in consideration.
|
||||
#notes-rgx=
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# class is considered mixin if its name matches the mixin-class-rgx option.
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# Tells whether to warn about missing members when the owner of the attribute
|
||||
# is inferred to be None.
|
||||
ignore-none=yes
|
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar
|
||||
# checks whenever an opaque object is returned when inferring. The inference
|
||||
# can return multiple potential results while evaluating a Python object, but
|
||||
# some branches might not be evaluated, which results in partial inference. In
|
||||
# that case, it might be useful to still emit no-member and other checks for
|
||||
# the rest of the inferred objects.
|
||||
ignore-on-opaque-inference=yes
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect
|
||||
# of finding the hint is based on edit distance.
|
||||
missing-member-hint=yes
|
||||
|
||||
# The minimum edit distance a name should have in order to be considered a
|
||||
# similar match for a missing member name.
|
||||
missing-member-hint-distance=1
|
||||
|
||||
# The total number of similar names that should be taken in consideration when
|
||||
# showing a hint for a missing member.
|
||||
missing-member-max-choices=1
|
||||
|
||||
# Regex pattern to define which classes are considered mixins ignore-mixin-
|
||||
# members is set to 'yes'
|
||||
mixin-class-rgx=.*[Mm]ixin
|
||||
|
||||
# List of decorators that change the signature of a decorated function.
|
||||
signature-mutators=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid defining new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# Tells whether unused global variables should be treated as a violation.
|
||||
allow-global-unused-variables=yes
|
||||
|
||||
# List of names allowed to shadow builtins
|
||||
allowed-redefined-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expected to
|
||||
# not be used).
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore.
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Maximum number of lines in a module.
|
||||
max-module-lines=1000
|
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body
|
||||
# contains single statement.
|
||||
single-line-class-stmt=no
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Comments are removed from the similarity computation
|
||||
ignore-comments=yes
|
||||
|
||||
# Docstrings are removed from the similarity computation
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Imports are removed from the similarity computation
|
||||
ignore-imports=no
|
||||
|
||||
# Signatures are removed from the similarity computation
|
||||
ignore-signatures=no
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Naming style matching correct argument names.
|
||||
argument-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct argument names. Overrides argument-
|
||||
# naming-style.
|
||||
#argument-rgx=
|
||||
|
||||
# Naming style matching correct attribute names.
|
||||
attr-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||
# style.
|
||||
#attr-rgx=
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma.
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
|
||||
# Bad variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be refused
|
||||
bad-names-rgxs=
|
||||
|
||||
# Naming style matching correct class attribute names.
|
||||
class-attribute-naming-style=any
|
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class-
|
||||
# attribute-naming-style.
|
||||
#class-attribute-rgx=
|
||||
|
||||
# Naming style matching correct class constant names.
|
||||
class-const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct class constant names. Overrides class-
|
||||
# const-naming-style.
|
||||
#class-const-rgx=
|
||||
|
||||
# Naming style matching correct class names.
|
||||
class-naming-style=PascalCase
|
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-
|
||||
# style.
|
||||
#class-rgx=
|
||||
|
||||
# Naming style matching correct constant names.
|
||||
const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming-
|
||||
# style.
|
||||
#const-rgx=
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
# Naming style matching correct function names.
|
||||
function-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct function names. Overrides function-
|
||||
# naming-style.
|
||||
#function-rgx=
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma.
|
||||
good-names=i,
|
||||
ii,
|
||||
jj,
|
||||
kk,
|
||||
dr,
|
||||
j,
|
||||
k,
|
||||
ex,
|
||||
Run,
|
||||
cb,
|
||||
_
|
||||
|
||||
# Good variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be accepted
|
||||
good-names-rgxs=.*scanID.*,.*RID.*,.*pointID.*,.*ID.*,.*_2D.*,.*_1D.*
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name.
|
||||
include-naming-hint=no
|
||||
|
||||
# Naming style matching correct inline iteration names.
|
||||
inlinevar-naming-style=any
|
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides
|
||||
# inlinevar-naming-style.
|
||||
#inlinevar-rgx=
|
||||
|
||||
# Naming style matching correct method names.
|
||||
method-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming-
|
||||
# style.
|
||||
#method-rgx=
|
||||
|
||||
# Naming style matching correct module names.
|
||||
module-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming-
|
||||
# style.
|
||||
#module-rgx=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
# These decorators are taken in consideration only for invalid-name.
|
||||
property-classes=abc.abstractproperty
|
||||
|
||||
# Naming style matching correct variable names.
|
||||
variable-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct variable names. Overrides variable-
|
||||
# naming-style.
|
||||
#variable-rgx=
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=no
|
||||
|
||||
# This flag controls whether the implicit-str-concat should generate a warning
|
||||
# on implicit string concatenation in sequences defined over several lines.
|
||||
check-str-concat-over-line-jumps=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# List of modules that can be imported at any level, not just the top level
|
||||
# one.
|
||||
allow-any-import-level=
|
||||
|
||||
# Allow wildcard imports from modules that define __all__.
|
||||
allow-wildcard-with-all=no
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma.
|
||||
deprecated-modules=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of external dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
ext-import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
||||
# external) dependencies to the given file (report RP0402 must not be
|
||||
# disabled).
|
||||
import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of internal dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
# Couples of modules and preferred modules, separated by a comma.
|
||||
preferred-modules=
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# Warn about protected attribute access inside special methods
|
||||
check-protected-access-in-special-methods=no
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp,
|
||||
__post_init__
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# List of regular expressions of class ancestor names to ignore when counting
|
||||
# public methods (see R0903)
|
||||
exclude-too-few-public-methods=
|
||||
|
||||
# List of qualified class names to ignore when counting class parents (see
|
||||
# R0901)
|
||||
ignored-parents=
|
||||
|
||||
# Maximum number of arguments for function / method.
|
||||
max-args=5
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Maximum number of boolean expressions in an if statement (see R0916).
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body.
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body.
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body.
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body.
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "BaseException, Exception".
|
||||
overgeneral-exceptions=BaseException,
|
||||
Exception
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
bec_atlas
|
32
LICENSE
Normal file
32
LICENSE
Normal file
@ -0,0 +1,32 @@
|
||||
The source code in this repository is licensed under a BSD 3-Clause
|
||||
license. Third-party dependencies may deviate from it.
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Paul Scherrer Institute
|
||||
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.
|
0
backend/bec_atlas/__init__.py
Normal file
0
backend/bec_atlas/__init__.py
Normal file
66
backend/bec_atlas/authentication.py
Normal file
66
backend/bec_atlas/authentication.py
Normal file
@ -0,0 +1,66 @@
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Annotated
|
||||
|
||||
import jwt
|
||||
from bec_atlas.datasources.scylladb import scylladb_schema as schema
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pwdlib import PasswordHash
|
||||
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/user/login/form")
|
||||
password_hash = PasswordHash.recommended()
|
||||
|
||||
|
||||
def get_secret_key():
|
||||
val = os.getenv("SECRET_KEY", "test_secret")
|
||||
return val
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return password_hash.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now() + expires_delta
|
||||
else:
|
||||
expire = datetime.now() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, get_secret_key(), algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def decode_token(token: str):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, get_secret_key(), algorithms=[ALGORITHM])
|
||||
return payload
|
||||
except InvalidTokenError as exc:
|
||||
raise credentials_exception from exc
|
||||
|
||||
|
||||
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> schema.User:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=401,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = decode_token(token)
|
||||
groups = payload.get("groups")
|
||||
email = payload.get("email")
|
||||
if not groups or not email:
|
||||
raise credentials_exception
|
||||
except Exception as exc:
|
||||
raise credentials_exception from exc
|
||||
return schema.User(groups=groups, email=email)
|
0
backend/bec_atlas/datasources/__init__.py
Normal file
0
backend/bec_atlas/datasources/__init__.py
Normal file
24
backend/bec_atlas/datasources/datasource_manager.py
Normal file
24
backend/bec_atlas/datasources/datasource_manager.py
Normal file
@ -0,0 +1,24 @@
|
||||
from bec_atlas.datasources.redis_datasource import RedisDatasource
|
||||
from bec_atlas.datasources.scylladb.scylladb import ScylladbDatasource
|
||||
|
||||
|
||||
class DatasourceManager:
|
||||
def __init__(self, config: dict):
|
||||
self.config = config
|
||||
self.datasources = {}
|
||||
self.load_datasources()
|
||||
|
||||
def connect(self):
|
||||
for datasource in self.datasources.values():
|
||||
datasource.connect()
|
||||
|
||||
def load_datasources(self):
|
||||
for datasource_name, datasource_config in self.config.items():
|
||||
if datasource_name == "scylla":
|
||||
self.datasources[datasource_name] = ScylladbDatasource(datasource_config)
|
||||
if datasource_name == "redis":
|
||||
self.datasources[datasource_name] = RedisDatasource(datasource_config)
|
||||
|
||||
def shutdown(self):
|
||||
for datasource in self.datasources.values():
|
||||
datasource.shutdown()
|
13
backend/bec_atlas/datasources/redis_datasource.py
Normal file
13
backend/bec_atlas/datasources/redis_datasource.py
Normal file
@ -0,0 +1,13 @@
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
|
||||
|
||||
class RedisDatasource:
|
||||
def __init__(self, config: dict):
|
||||
self.config = config
|
||||
self.connector = RedisConnector(f"{config.get('host')}:{config.get('port')}")
|
||||
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
self.connector.shutdown()
|
0
backend/bec_atlas/datasources/scylladb/__init__.py
Normal file
0
backend/bec_atlas/datasources/scylladb/__init__.py
Normal file
112
backend/bec_atlas/datasources/scylladb/scylladb.py
Normal file
112
backend/bec_atlas/datasources/scylladb/scylladb.py
Normal file
@ -0,0 +1,112 @@
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from bec_atlas.authentication import get_password_hash
|
||||
from bec_atlas.datasources.scylladb import scylladb_schema as schema
|
||||
from cassandra.cluster import Cluster
|
||||
from cassandra.cqlengine import columns, connection
|
||||
from cassandra.cqlengine.management import create_keyspace_simple, sync_table
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ScylladbDatasource:
|
||||
KEYSPACE = "bec_atlas"
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.cluster = None
|
||||
self.session = None
|
||||
|
||||
def connect(self):
|
||||
self.start_client()
|
||||
self.load_functional_accounts()
|
||||
|
||||
def start_client(self):
|
||||
"""
|
||||
Start the ScyllaDB client by creating a Cluster object and a Session object.
|
||||
"""
|
||||
hosts = self.config.get("hosts")
|
||||
if not hosts:
|
||||
raise ValueError("Hosts are not provided in the configuration")
|
||||
|
||||
#
|
||||
connection.setup(hosts, self.KEYSPACE, protocol_version=3)
|
||||
create_keyspace_simple(self.KEYSPACE, 1)
|
||||
self._sync_tables()
|
||||
self.cluster = Cluster(hosts)
|
||||
self.session = self.cluster.connect()
|
||||
|
||||
def _sync_tables(self):
|
||||
"""
|
||||
Sync the tables with the schema defined in the scylladb_schema.py file.
|
||||
"""
|
||||
sync_table(schema.Realm)
|
||||
sync_table(schema.Deployments)
|
||||
sync_table(schema.Experiments)
|
||||
sync_table(schema.StateCondition)
|
||||
sync_table(schema.State)
|
||||
sync_table(schema.Session)
|
||||
sync_table(schema.Datasets)
|
||||
sync_table(schema.DatasetUserData)
|
||||
sync_table(schema.Scan)
|
||||
sync_table(schema.ScanUserData)
|
||||
sync_table(schema.ScanData)
|
||||
sync_table(schema.SignalDataInt)
|
||||
sync_table(schema.SignalDataFloat)
|
||||
sync_table(schema.SignalDataString)
|
||||
sync_table(schema.SignalDataBool)
|
||||
sync_table(schema.SignalDataBlob)
|
||||
sync_table(schema.SignalDataDateTime)
|
||||
sync_table(schema.SignalDataUUID)
|
||||
sync_table(schema.User)
|
||||
sync_table(schema.UserCredentials)
|
||||
|
||||
def load_functional_accounts(self):
|
||||
"""
|
||||
Load the functional accounts to the database.
|
||||
"""
|
||||
functional_accounts_file = os.path.join(
|
||||
os.path.dirname(__file__), "functional_accounts.json"
|
||||
)
|
||||
with open(functional_accounts_file, "r", encoding="utf-8") as file:
|
||||
functional_accounts = json.load(file)
|
||||
|
||||
for account in functional_accounts:
|
||||
# check if the account already exists in the database
|
||||
password_hash = get_password_hash(account.pop("password"))
|
||||
result = schema.User.objects.filter(email=account["email"])
|
||||
if result.count() > 0:
|
||||
continue
|
||||
user = schema.User.create(**account)
|
||||
|
||||
schema.UserCredentials.create(user_id=user.user_id, password=password_hash)
|
||||
|
||||
def get(self, table_name: str, filter: str = None, parameters: tuple = None):
|
||||
"""
|
||||
Get the data from the specified table.
|
||||
"""
|
||||
# schema.User.objects.get(email=)
|
||||
if filter:
|
||||
query = f"SELECT * FROM {self.KEYSPACE}.{table_name} WHERE {filter};"
|
||||
else:
|
||||
query = f"SELECT * FROM {self.KEYSPACE}.{table_name};"
|
||||
if parameters:
|
||||
return self.session.execute(query, parameters)
|
||||
return self.session.execute(query)
|
||||
|
||||
def post(self, table_name: str, data: BaseModel):
|
||||
"""
|
||||
Post the data to the specified table.
|
||||
|
||||
Args:
|
||||
table_name (str): The name of the table to post the data.
|
||||
data (BaseModel): The data to be posted.
|
||||
|
||||
"""
|
||||
query = f"INSERT INTO {self.KEYSPACE}.{table_name} JSON '{data.model_dump_json(exclude_none=True)}';"
|
||||
return self.session.execute(query)
|
||||
|
||||
def shutdown(self):
|
||||
self.cluster.shutdown()
|
139
backend/bec_atlas/datasources/scylladb/scylladb_schema.py
Normal file
139
backend/bec_atlas/datasources/scylladb/scylladb_schema.py
Normal file
@ -0,0 +1,139 @@
|
||||
import uuid
|
||||
|
||||
from cassandra.cqlengine import columns
|
||||
from cassandra.cqlengine.models import Model
|
||||
|
||||
|
||||
class User(Model):
|
||||
email = columns.Text(primary_key=True)
|
||||
user_id = columns.UUID(default=uuid.uuid4())
|
||||
first_name = columns.Text()
|
||||
last_name = columns.Text()
|
||||
groups = columns.Set(columns.Text)
|
||||
created_at = columns.DateTime()
|
||||
updated_at = columns.DateTime()
|
||||
|
||||
|
||||
class UserCredentials(Model):
|
||||
user_id = columns.UUID(primary_key=True)
|
||||
password = columns.Text()
|
||||
|
||||
|
||||
class Realm(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
deployment_id = columns.Text(primary_key=True)
|
||||
name = columns.Text()
|
||||
|
||||
|
||||
class Deployments(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
deployment_id = columns.Text(primary_key=True)
|
||||
name = columns.Text()
|
||||
active_session_id = columns.UUID()
|
||||
|
||||
|
||||
class Experiments(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
pgroup = columns.Text(primary_key=True)
|
||||
proposal = columns.Text()
|
||||
text = columns.Text()
|
||||
|
||||
|
||||
class StateCondition(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
name = columns.Text(primary_key=True)
|
||||
description = columns.Text()
|
||||
device = columns.Text()
|
||||
signal_value = columns.Text()
|
||||
signal_type = columns.Text()
|
||||
tolerance = columns.Text()
|
||||
|
||||
|
||||
class State(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
name = columns.Text(primary_key=True)
|
||||
description = columns.Text()
|
||||
conditions = columns.List(columns.Text)
|
||||
|
||||
|
||||
class Session(Model):
|
||||
realm_id = columns.Text(primary_key=True)
|
||||
session_id = columns.UUID(primary_key=True)
|
||||
config = columns.Text()
|
||||
|
||||
|
||||
class Datasets(Model):
|
||||
session_id = columns.UUID(primary_key=True)
|
||||
dataset_id = columns.UUID(primary_key=True)
|
||||
scan_id = columns.UUID()
|
||||
|
||||
|
||||
class DatasetUserData(Model):
|
||||
dataset_id = columns.UUID(primary_key=True)
|
||||
name = columns.Text()
|
||||
rating = columns.Integer()
|
||||
comments = columns.Text()
|
||||
preview = columns.Blob()
|
||||
|
||||
|
||||
class Scan(Model):
|
||||
session_id = columns.UUID(primary_key=True)
|
||||
scan_id = columns.UUID(primary_key=True)
|
||||
scan_number = columns.Integer()
|
||||
name = columns.Text()
|
||||
scan_class = columns.Text()
|
||||
parameters = columns.Text()
|
||||
start_time = columns.DateTime()
|
||||
end_time = columns.DateTime()
|
||||
exit_status = columns.Text()
|
||||
|
||||
|
||||
class ScanUserData(Model):
|
||||
scan_id = columns.UUID(primary_key=True)
|
||||
name = columns.Text()
|
||||
rating = columns.Integer()
|
||||
comments = columns.Text()
|
||||
preview = columns.Blob()
|
||||
|
||||
|
||||
class ScanData(Model):
|
||||
scan_id = columns.UUID(primary_key=True)
|
||||
device_name = columns.Text(primary_key=True)
|
||||
signal_name = columns.Text(primary_key=True)
|
||||
shape = columns.List(columns.Integer)
|
||||
dtype = columns.Text()
|
||||
|
||||
|
||||
class SignalDataBase(Model):
|
||||
realm_id = columns.Text(partition_key=True)
|
||||
signal_name = columns.Text(partition_key=True)
|
||||
scan_id = columns.UUID(primary_key=True)
|
||||
index = columns.Integer(primary_key=True)
|
||||
|
||||
|
||||
class SignalDataInt(SignalDataBase):
|
||||
data = columns.Integer()
|
||||
|
||||
|
||||
class SignalDataFloat(SignalDataBase):
|
||||
data = columns.Float()
|
||||
|
||||
|
||||
class SignalDataString(SignalDataBase):
|
||||
data = columns.Text()
|
||||
|
||||
|
||||
class SignalDataBlob(SignalDataBase):
|
||||
data = columns.Blob()
|
||||
|
||||
|
||||
class SignalDataBool(SignalDataBase):
|
||||
data = columns.Boolean()
|
||||
|
||||
|
||||
class SignalDataDateTime(SignalDataBase):
|
||||
data = columns.DateTime()
|
||||
|
||||
|
||||
class SignalDataUUID(SignalDataBase):
|
||||
data = columns.UUID()
|
51
backend/bec_atlas/main.py
Normal file
51
backend/bec_atlas/main.py
Normal file
@ -0,0 +1,51 @@
|
||||
import socketio
|
||||
import uvicorn
|
||||
from bec_atlas.datasources.datasource_manager import DatasourceManager
|
||||
from bec_atlas.router.redis_router import RedisRouter, RedisWebsocket
|
||||
from bec_atlas.router.scan_router import ScanRouter
|
||||
from bec_atlas.router.user import UserRouter
|
||||
from fastapi import FastAPI
|
||||
|
||||
CONFIG = {"redis": {"host": "localhost", "port": 6379}, "scylla": {"hosts": ["localhost"]}}
|
||||
|
||||
|
||||
class HorizonApp:
|
||||
API_VERSION = "v1"
|
||||
|
||||
def __init__(self):
|
||||
self.app = FastAPI()
|
||||
self.prefix = f"/api/{self.API_VERSION}"
|
||||
self.datasources = DatasourceManager(config=CONFIG)
|
||||
self.register_event_handler()
|
||||
self.add_routers()
|
||||
|
||||
def register_event_handler(self):
|
||||
self.app.add_event_handler("startup", self.on_startup)
|
||||
self.app.add_event_handler("shutdown", self.on_shutdown)
|
||||
|
||||
async def on_startup(self):
|
||||
self.datasources.connect()
|
||||
|
||||
async def on_shutdown(self):
|
||||
self.datasources.shutdown()
|
||||
|
||||
def add_routers(self):
|
||||
if not self.datasources.datasources:
|
||||
raise ValueError("Datasources not loaded")
|
||||
if "scylla" in self.datasources.datasources:
|
||||
self.scan_router = ScanRouter(prefix=self.prefix, datasources=self.datasources)
|
||||
self.app.include_router(self.scan_router.router)
|
||||
self.user_router = UserRouter(prefix=self.prefix, datasources=self.datasources)
|
||||
self.app.include_router(self.user_router.router)
|
||||
|
||||
if "redis" in self.datasources.datasources:
|
||||
self.redis_websocket = RedisWebsocket(prefix=self.prefix, datasources=self.datasources)
|
||||
self.app.mount("/", self.redis_websocket.app)
|
||||
|
||||
def run(self):
|
||||
uvicorn.run(self.app, host="localhost", port=8000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
horizon_app = HorizonApp()
|
||||
horizon_app.run()
|
0
backend/bec_atlas/router/__init__.py
Normal file
0
backend/bec_atlas/router/__init__.py
Normal file
4
backend/bec_atlas/router/base_router.py
Normal file
4
backend/bec_atlas/router/base_router.py
Normal file
@ -0,0 +1,4 @@
|
||||
class BaseRouter:
|
||||
def __init__(self, prefix: str = "/api/v1", datasources=None) -> None:
|
||||
self.datasources = datasources
|
||||
self.prefix = prefix
|
98
backend/bec_atlas/router/redis_router.py
Normal file
98
backend/bec_atlas/router/redis_router.py
Normal file
@ -0,0 +1,98 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import socketio
|
||||
from bec_atlas.router.base_router import BaseRouter
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
|
||||
|
||||
class RedisRouter(BaseRouter):
|
||||
"""
|
||||
This class is a router for the Redis API. It exposes the redis client through
|
||||
the API. For pub/sub and stream operations, a websocket connection can be used.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix="/api/v1", datasources=None):
|
||||
super().__init__(prefix, datasources)
|
||||
self.redis = self.datasources.datasources["redis"].connector
|
||||
self.router = APIRouter(prefix=prefix)
|
||||
self.router.add_api_route("/redis", self.redis_get, methods=["GET"])
|
||||
self.router.add_api_route("/redis", self.redis_post, methods=["POST"])
|
||||
self.router.add_api_route("/redis", self.redis_delete, methods=["DELETE"])
|
||||
|
||||
async def redis_get(self, key: str):
|
||||
return self.redis.get(key)
|
||||
|
||||
async def redis_post(self, key: str, value: str):
|
||||
return self.redis.set(key, value)
|
||||
|
||||
async def redis_delete(self, key: str):
|
||||
return self.redis.delete(key)
|
||||
|
||||
|
||||
class RedisWebsocket:
|
||||
"""
|
||||
This class is a websocket handler for the Redis API. It exposes the redis client through
|
||||
the websocket.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix="/api/v1", datasources=None):
|
||||
self.redis: RedisConnector = datasources.datasources["redis"].connector
|
||||
self.prefix = prefix
|
||||
self.active_connections = set()
|
||||
self.socket = socketio.AsyncServer(cors_allowed_origins="*", async_mode="asgi")
|
||||
self.app = socketio.ASGIApp(self.socket)
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
self.socket.on("connect", self.connect_client)
|
||||
self.socket.on("register", self.redis_register)
|
||||
self.socket.on("disconnect", self.disconnect_client)
|
||||
|
||||
def connect_client(self, sid, environ):
|
||||
print("Client connected")
|
||||
self.active_connections.add(sid)
|
||||
|
||||
def disconnect_client(self, sid, _environ):
|
||||
print("Client disconnected")
|
||||
self.active_connections.pop(sid)
|
||||
|
||||
async def redis_register(self, sid: str, msg: str):
|
||||
if sid not in self.active_connections:
|
||||
self.active_connections.add(sid)
|
||||
try:
|
||||
data = json.loads(msg)
|
||||
except json.JSONDecodeError:
|
||||
return
|
||||
|
||||
endpoint = getattr(MessageEndpoints, data.get("endpoint"))
|
||||
|
||||
# check if the endpoint receives arguments
|
||||
if len(inspect.signature(endpoint).parameters) > 1:
|
||||
endpoint = endpoint(data.get("args"))
|
||||
else:
|
||||
endpoint = endpoint()
|
||||
|
||||
self.redis.register(endpoint, cb=self.on_redis_message, parent=self)
|
||||
await self.socket.enter_room(sid, endpoint.endpoint)
|
||||
await self.socket.emit("registered", data={"endpoint": endpoint.endpoint}, room=sid)
|
||||
|
||||
@staticmethod
|
||||
def on_redis_message(message, parent):
|
||||
async def emit_message(message):
|
||||
outgoing = {
|
||||
"data": message.value.model_dump_json(),
|
||||
"message_type": message.value.__class__.__name__,
|
||||
}
|
||||
await parent.socket.emit("new_message", data=outgoing, room=message.topic)
|
||||
|
||||
# check that the event loop is running
|
||||
if not parent.loop.is_running():
|
||||
parent.loop.run_until_complete(emit_message(message))
|
||||
else:
|
||||
asyncio.run_coroutine_threadsafe(emit_message(message), parent.loop)
|
19
backend/bec_atlas/router/scan_router.py
Normal file
19
backend/bec_atlas/router/scan_router.py
Normal file
@ -0,0 +1,19 @@
|
||||
from bec_atlas.authentication import get_current_user
|
||||
from bec_atlas.models import User
|
||||
from bec_atlas.router.base_router import BaseRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
|
||||
class ScanRouter(BaseRouter):
|
||||
def __init__(self, prefix="/api/v1", datasources=None):
|
||||
super().__init__(prefix, datasources)
|
||||
self.scylla = self.datasources.datasources.get("scylla")
|
||||
self.router = APIRouter(prefix=prefix)
|
||||
self.router.add_api_route("/scan", self.scan, methods=["GET"])
|
||||
self.router.add_api_route("/scan/{scan_id}", self.scan_with_id, methods=["GET"])
|
||||
|
||||
async def scan(self, current_user: User = Depends(get_current_user)):
|
||||
return self.scylla.get("scan", current_user=current_user)
|
||||
|
||||
async def scan_with_id(self, scan_id: str):
|
||||
return {"scan_id": scan_id}
|
45
backend/bec_atlas/router/user.py
Normal file
45
backend/bec_atlas/router/user.py
Normal file
@ -0,0 +1,45 @@
|
||||
from typing import Annotated
|
||||
|
||||
from bec_atlas.authentication import create_access_token, get_current_user, verify_password
|
||||
from bec_atlas.datasources.scylladb import scylladb_schema as schema
|
||||
from bec_atlas.models import User
|
||||
from bec_atlas.router.base_router import BaseRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
|
||||
class UserRouter(BaseRouter):
|
||||
def __init__(self, prefix="/api/v1", datasources=None):
|
||||
super().__init__(prefix, datasources)
|
||||
self.scylla = self.datasources.datasources.get("scylla")
|
||||
self.router = APIRouter(prefix=prefix)
|
||||
self.router.add_api_route("/user/me", self.user_me, methods=["GET"])
|
||||
self.router.add_api_route("/user/login", self.user_login, methods=["POST"], dependencies=[])
|
||||
self.router.add_api_route(
|
||||
"/user/login/form", self.form_login, methods=["POST"], dependencies=[]
|
||||
)
|
||||
|
||||
async def user_me(self, user: User = Depends(get_current_user)):
|
||||
data = schema.User.objects.filter(email=user.email)
|
||||
if data.count() == 0:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return data.first()
|
||||
|
||||
async def form_login(self, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
|
||||
out = await self.user_login(form_data.username, form_data.password)
|
||||
return {"access_token": out, "token_type": "bearer"}
|
||||
|
||||
async def user_login(self, username: str, password: str):
|
||||
result = schema.User.objects.filter(email=username)
|
||||
if result.count() == 0:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
user: schema.User = result.first()
|
||||
credentials = schema.UserCredentials.objects.filter(user_id=user.user_id)
|
||||
if credentials.count() == 0:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
user_credentials = credentials.first()
|
||||
if not verify_password(password, user_credentials.password):
|
||||
raise HTTPException(status_code=401, detail="Invalid password")
|
||||
|
||||
return create_access_token(data={"groups": list(user.groups), "email": user.email})
|
92
backend/pyproject.toml
Normal file
92
backend/pyproject.toml
Normal file
@ -0,0 +1,92 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_atlas"
|
||||
version = "0.0.0"
|
||||
description = "BEC Atlas"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Scientific/Engineering",
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi[standard]",
|
||||
"pyjwt",
|
||||
"pwdlib[argon2]",
|
||||
"scylla-driver",
|
||||
"bec_lib",
|
||||
"python-socketio[asyncio_client]",
|
||||
]
|
||||
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"coverage~=7.0",
|
||||
"pytest-random-order~=1.1",
|
||||
"pytest-timeout~=2.2",
|
||||
"pytest~=8.0",
|
||||
]
|
||||
|
||||
|
||||
[project.urls]
|
||||
"Bug Tracker" = "https://gitlab.psi.ch/bec/bec_atlas/issues"
|
||||
Homepage = "https://gitlab.psi.ch/bec/bec_atlas"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["*"]
|
||||
exclude = ["docs/**", "tests/**"]
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
include = ["*"]
|
||||
exclude = ["docs/**", "tests/**"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
skip-magic-trailing-comma = true
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 100
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
known_first_party = ["bec_widgets"]
|
||||
|
||||
[tool.semantic_release]
|
||||
build_command = "python -m build"
|
||||
version_toml = ["pyproject.toml:project.version"]
|
||||
|
||||
[tool.semantic_release.commit_author]
|
||||
env = "GIT_COMMIT_AUTHOR"
|
||||
default = "semantic-release <semantic-release>"
|
||||
|
||||
[tool.semantic_release.commit_parser_options]
|
||||
allowed_tags = [
|
||||
"build",
|
||||
"chore",
|
||||
"ci",
|
||||
"docs",
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"style",
|
||||
"refactor",
|
||||
"test",
|
||||
]
|
||||
minor_tags = ["feat"]
|
||||
patch_tags = ["fix", "perf"]
|
||||
default_bump_level = 0
|
||||
|
||||
[tool.semantic_release.remote]
|
||||
name = "origin"
|
||||
type = "gitlab"
|
||||
ignore_token_for_push = false
|
||||
|
||||
[tool.semantic_release.remote.token]
|
||||
env = "GL_TOKEN"
|
||||
|
||||
[tool.semantic_release.publish]
|
||||
dist_glob_patterns = ["dist/*"]
|
||||
upload_to_vcs_release = true
|
Reference in New Issue
Block a user