astrospace 1.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. astrospace-1.2.8/.gitignore +191 -0
  2. astrospace-1.2.8/AstroSpace/__init__.py +112 -0
  3. astrospace-1.2.8/AstroSpace/__main__.py +44 -0
  4. astrospace-1.2.8/AstroSpace/auth.py +105 -0
  5. astrospace-1.2.8/AstroSpace/blog.py +679 -0
  6. astrospace-1.2.8/AstroSpace/config.py +28 -0
  7. astrospace-1.2.8/AstroSpace/constants.py +79 -0
  8. astrospace-1.2.8/AstroSpace/db.py +161 -0
  9. astrospace-1.2.8/AstroSpace/logging_utils.py +94 -0
  10. astrospace-1.2.8/AstroSpace/profile/__init__.py +0 -0
  11. astrospace-1.2.8/AstroSpace/profile/private.py +761 -0
  12. astrospace-1.2.8/AstroSpace/profile/public.py +38 -0
  13. astrospace-1.2.8/AstroSpace/repositories/__init__.py +1 -0
  14. astrospace-1.2.8/AstroSpace/repositories/images.py +444 -0
  15. astrospace-1.2.8/AstroSpace/schema.sql +344 -0
  16. astrospace-1.2.8/AstroSpace/services/__init__.py +1 -0
  17. astrospace-1.2.8/AstroSpace/services/authorization.py +18 -0
  18. astrospace-1.2.8/AstroSpace/services/collection_filters.py +244 -0
  19. astrospace-1.2.8/AstroSpace/services/content.py +38 -0
  20. astrospace-1.2.8/AstroSpace/services/uploads.py +34 -0
  21. astrospace-1.2.8/AstroSpace/static/assets/images/default_profile.png +0 -0
  22. astrospace-1.2.8/AstroSpace/static/assets/images/moon.png +0 -0
  23. astrospace-1.2.8/AstroSpace/static/assets/images/pixinsight-140x40-black.en.png +0 -0
  24. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Black.woff2 +0 -0
  25. astrospace-1.2.8/AstroSpace/static/fonts/Lato-BlackItalic.woff2 +0 -0
  26. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Bold.woff2 +0 -0
  27. astrospace-1.2.8/AstroSpace/static/fonts/Lato-BoldItalic.woff2 +0 -0
  28. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Hairline.woff2 +0 -0
  29. astrospace-1.2.8/AstroSpace/static/fonts/Lato-HairlineItalic.woff2 +0 -0
  30. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Heavy.woff2 +0 -0
  31. astrospace-1.2.8/AstroSpace/static/fonts/Lato-HeavyItalic.woff2 +0 -0
  32. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Italic.woff2 +0 -0
  33. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Light.woff2 +0 -0
  34. astrospace-1.2.8/AstroSpace/static/fonts/Lato-LightItalic.woff2 +0 -0
  35. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Medium.woff2 +0 -0
  36. astrospace-1.2.8/AstroSpace/static/fonts/Lato-MediumItalic.woff2 +0 -0
  37. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Regular.woff2 +0 -0
  38. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Semibold.woff2 +0 -0
  39. astrospace-1.2.8/AstroSpace/static/fonts/Lato-SemiboldItalic.woff2 +0 -0
  40. astrospace-1.2.8/AstroSpace/static/fonts/Lato-Thin.woff2 +0 -0
  41. astrospace-1.2.8/AstroSpace/static/fonts/Lato-ThinItalic.woff2 +0 -0
  42. astrospace-1.2.8/AstroSpace/static/fonts/Patung.woff2 +0 -0
  43. astrospace-1.2.8/AstroSpace/static/fonts/Scribble.woff2 +0 -0
  44. astrospace-1.2.8/AstroSpace/static/input.css +169 -0
  45. astrospace-1.2.8/AstroSpace/static/js/binds.js +205 -0
  46. astrospace-1.2.8/AstroSpace/static/js/fitsUtils.js +62 -0
  47. astrospace-1.2.8/AstroSpace/static/js/phd2Plotly.js +454 -0
  48. astrospace-1.2.8/AstroSpace/static/js/plotHR.js +71 -0
  49. astrospace-1.2.8/AstroSpace/static/js/subFramePlot.js +145 -0
  50. astrospace-1.2.8/AstroSpace/static/js/utils.js +18 -0
  51. astrospace-1.2.8/AstroSpace/static/styles.css +2578 -0
  52. astrospace-1.2.8/AstroSpace/templates/auth/auth.html +26 -0
  53. astrospace-1.2.8/AstroSpace/templates/base.html +217 -0
  54. astrospace-1.2.8/AstroSpace/templates/collection.html +474 -0
  55. astrospace-1.2.8/AstroSpace/templates/create.html +611 -0
  56. astrospace-1.2.8/AstroSpace/templates/home.html +98 -0
  57. astrospace-1.2.8/AstroSpace/templates/image_detail.html +1175 -0
  58. astrospace-1.2.8/AstroSpace/templates/profile.html +539 -0
  59. astrospace-1.2.8/AstroSpace/templates/public_profile.html +61 -0
  60. astrospace-1.2.8/AstroSpace/utils/__init__.py +0 -0
  61. astrospace-1.2.8/AstroSpace/utils/moon_phase.py +38 -0
  62. astrospace-1.2.8/AstroSpace/utils/phd2logparser.py +324 -0
  63. astrospace-1.2.8/AstroSpace/utils/platesolve.py +530 -0
  64. astrospace-1.2.8/AstroSpace/utils/queries.py +15 -0
  65. astrospace-1.2.8/AstroSpace/utils/simbad_object_description.json +187 -0
  66. astrospace-1.2.8/AstroSpace/utils/utils.py +72 -0
  67. astrospace-1.2.8/AstroSpace/utils/xisf_reader.py +184 -0
  68. astrospace-1.2.8/LICENSE +674 -0
  69. astrospace-1.2.8/PKG-INFO +217 -0
  70. astrospace-1.2.8/README.md +191 -0
  71. astrospace-1.2.8/pyproject.toml +52 -0
@@ -0,0 +1,191 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ hatch.bat
6
+ nginx/.env
7
+ # C extensions
8
+ *.so
9
+ dist/
10
+ instance/
11
+ blogs/
12
+ postgres/
13
+ **/node_modules/
14
+ **/uploads/
15
+ **/watch.bat
16
+ serve.bat
17
+ cmd.txt
18
+ .vscode/
19
+ # Distribution / packaging
20
+ .Python
21
+ build/
22
+ develop-eggs/
23
+ dist/
24
+ downloads/
25
+ eggs/
26
+ .eggs/
27
+ lib/
28
+ lib64/
29
+ parts/
30
+ sdist/
31
+ var/
32
+ wheels/
33
+ share/python-wheels/
34
+ *.egg-info/
35
+ .installed.cfg
36
+ *.egg
37
+ MANIFEST
38
+ publish/
39
+ # PyInstaller
40
+ # Usually these files are written by a python script from a template
41
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
42
+ *.manifest
43
+ *.spec
44
+
45
+ # Installer logs
46
+ pip-log.txt
47
+ pip-delete-this-directory.txt
48
+
49
+ # Unit test / coverage reports
50
+ htmlcov/
51
+ .tox/
52
+ .nox/
53
+ .coverage
54
+ .coverage.*
55
+ .cache
56
+ nosetests.xml
57
+ coverage.xml
58
+ *.cover
59
+ *.py,cover
60
+ .hypothesis/
61
+ .pytest_cache/
62
+ cover/
63
+
64
+ # Translations
65
+ *.mo
66
+ *.pot
67
+
68
+ # Django stuff:
69
+ *.log
70
+ local_settings.py
71
+ db.sqlite3
72
+ db.sqlite3-journal
73
+
74
+ # Flask stuff:
75
+ instance/
76
+ .webassets-cache
77
+
78
+ # Scrapy stuff:
79
+ .scrapy
80
+
81
+ # Sphinx documentation
82
+ docs/_build/
83
+
84
+ # PyBuilder
85
+ .pybuilder/
86
+ target/
87
+
88
+ # Jupyter Notebook
89
+ .ipynb_checkpoints
90
+
91
+ # IPython
92
+ profile_default/
93
+ ipython_config.py
94
+
95
+ # pyenv
96
+ # For a library or package, you might want to ignore these files since the code is
97
+ # intended to run in multiple environments; otherwise, check them in:
98
+ # .python-version
99
+
100
+ # pipenv
101
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
102
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
103
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
104
+ # install all needed dependencies.
105
+ #Pipfile.lock
106
+
107
+ # UV
108
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
109
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
110
+ # commonly ignored for libraries.
111
+ #uv.lock
112
+
113
+ # poetry
114
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
115
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
116
+ # commonly ignored for libraries.
117
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
118
+ #poetry.lock
119
+
120
+ # pdm
121
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
122
+ #pdm.lock
123
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
124
+ # in version control.
125
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
126
+ .pdm.toml
127
+ .pdm-python
128
+ .pdm-build/
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
131
+ __pypackages__/
132
+
133
+ # Celery stuff
134
+ celerybeat-schedule
135
+ celerybeat.pid
136
+
137
+ # SageMath parsed files
138
+ *.sage.py
139
+
140
+ # Environments
141
+ .env
142
+ .venv
143
+ env/
144
+ venv/
145
+ ENV/
146
+ env.bak/
147
+ venv.bak/
148
+
149
+ # Spyder project settings
150
+ .spyderproject
151
+ .spyproject
152
+
153
+ # Rope project settings
154
+ .ropeproject
155
+
156
+ # mkdocs documentation
157
+ /site
158
+
159
+ # mypy
160
+ .mypy_cache/
161
+ .dmypy.json
162
+ dmypy.json
163
+
164
+ # Pyre type checker
165
+ .pyre/
166
+
167
+ # pytype static type analyzer
168
+ .pytype/
169
+
170
+ # Cython debug symbols
171
+ cython_debug/
172
+
173
+ # PyCharm
174
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
175
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
176
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
177
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
178
+ #.idea/
179
+
180
+ # Ruff stuff:
181
+ .ruff_cache/
182
+
183
+ # PyPI configuration file
184
+ .pypirc
185
+
186
+ # Cursor
187
+ # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
188
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
189
+ # refer to https://docs.cursor.com/context/ignore-files
190
+ .cursorignore
191
+ .cursorindexingignore
@@ -0,0 +1,112 @@
1
+ import os
2
+ import logging
3
+ from flask import Flask, jsonify, g
4
+ from flask_wtf.csrf import CSRFProtect
5
+ from werkzeug.exceptions import RequestEntityTooLarge
6
+ from AstroSpace.logging_utils import configure_app_logging, debug_log
7
+
8
+ csrf = CSRFProtect()
9
+
10
+
11
+ def create_app(test_config=None):
12
+ # create and configure the app
13
+ app = Flask(__name__, instance_relative_config=True)
14
+
15
+ # first priority
16
+ app.config.from_object('AstroSpace.config.Config')
17
+
18
+ # second priority
19
+ # load the environment variables from the path set in environment variable
20
+ app.config.from_envvar('ASTROSPACE_SETTINGS', silent=True)
21
+
22
+ if test_config is None:
23
+ # load the instance config, if it exists, when not testing
24
+ app.config.from_pyfile('config.py', silent=True)
25
+ else:
26
+ # load the test config if passed in
27
+ app.config.from_mapping(test_config)
28
+
29
+ app.config['root_path'] = os.path.dirname(__file__)
30
+ app.config['MAX_CONTENT_LENGTH'] = 2 * (1024 ** 3) #about 2GB
31
+ configure_app_logging(app)
32
+
33
+ @app.errorhandler(RequestEntityTooLarge)
34
+ def handle_file_too_large(e):
35
+ return jsonify(error=f"Uploaded File is too large. Max size is {app.config['MAX_CONTENT_LENGTH']/(1024**3)} GB."), 413
36
+
37
+ #app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
38
+ os.makedirs(app.config['UPLOAD_PATH'],exist_ok=True)
39
+ skip_db_init = app.config.get("SKIP_DB_INIT", False)
40
+ debug_log(
41
+ "Application configured (instance_path=%s, skip_db_init=%s, max_upload_bytes=%s)",
42
+ app.instance_path,
43
+ skip_db_init,
44
+ app.config["MAX_CONTENT_LENGTH"],
45
+ )
46
+
47
+ # ensure the instance folder exists
48
+ try:
49
+ os.makedirs(app.instance_path)
50
+ except OSError:
51
+ pass
52
+
53
+ for key in ['SECRET_KEY', 'DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT']:
54
+ if key not in app.config:
55
+ raise ValueError(f"{key} must be set in the configuration", app.config)
56
+ if not app.config.get("SECRET_KEY"):
57
+ raise ValueError("SECRET_KEY must be set in the configuration")
58
+
59
+ csrf.init_app(app)
60
+
61
+ from . import db
62
+ if not skip_db_init:
63
+ db.init_app(app)
64
+
65
+ with app.app_context():
66
+ exists = db.check_images_table_exists()
67
+ debug_log("Database bootstrap check completed (images_table_exists=%s)", exists)
68
+ if not exists:
69
+ debug_log(
70
+ "images table is missing; initializing database schema.",
71
+ level=logging.INFO,
72
+ )
73
+ db.init_db()
74
+ db.ensure_runtime_schema()
75
+
76
+ @app.before_request
77
+ def load_web_info():
78
+ if skip_db_init:
79
+ g.web_info = {}
80
+ return
81
+
82
+ conn = db.get_conn()
83
+ with conn.cursor() as cur:
84
+ cur.execute("SELECT * FROM web_info LIMIT 1")
85
+ info = cur.fetchone()
86
+
87
+ g.web_info = info or {} # fallback to empty dict
88
+
89
+ @app.context_processor
90
+ def inject_web_info():
91
+ return {
92
+ "web_info": getattr(g, "web_info", {})
93
+ }
94
+
95
+ from . import auth
96
+ app.register_blueprint(auth.bp)
97
+
98
+ from . import blog
99
+ app.register_blueprint(blog.bp)
100
+
101
+ from .profile import private, public
102
+ app.register_blueprint(private.bp)
103
+ app.register_blueprint(public.bp)
104
+
105
+ app.add_url_rule('/', endpoint='index')
106
+
107
+ return app
108
+
109
+
110
+ if __name__ == "__main__":
111
+ app = create_app()
112
+ app.run(debug=True, host="0.0.0.0", port=9000)
@@ -0,0 +1,44 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+
5
+ from AstroSpace.logging_utils import debug_log, runtime_debug_enabled, strip_debug_flag
6
+
7
+
8
+ DEFAULT_GUNICORN_OPTIONS = [
9
+ "-w",
10
+ "3",
11
+ "-b",
12
+ "0.0.0.0:9000",
13
+ "--timeout",
14
+ "1000",
15
+ "--worker-class",
16
+ "gevent",
17
+ ]
18
+ APP_TARGET = "AstroSpace:create_app()"
19
+
20
+
21
+ def build_gunicorn_command(argv=None):
22
+ args = list(sys.argv[1:] if argv is None else argv)
23
+ filtered_args, flag_requested = strip_debug_flag(args)
24
+ debug_enabled = flag_requested or runtime_debug_enabled(filtered_args)
25
+
26
+ command = ["gunicorn", *DEFAULT_GUNICORN_OPTIONS]
27
+ if debug_enabled:
28
+ command.extend(["--log-level", "debug"])
29
+ command.extend(filtered_args)
30
+ command.append(APP_TARGET)
31
+ return command, debug_enabled
32
+
33
+
34
+ def main(argv=None):
35
+ command, debug_enabled = build_gunicorn_command(argv)
36
+ if debug_enabled:
37
+ os.environ["ASTROSPACE_DEBUG"] = "1"
38
+ debug_log("Starting Gunicorn with AstroSpace debug logging enabled.", level=logging.INFO)
39
+
40
+ os.execvp(command[0], command)
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()
@@ -0,0 +1,105 @@
1
+ import functools
2
+
3
+ from psycopg2 import IntegrityError
4
+ from flask import (
5
+ Blueprint, flash, g, redirect, render_template, request, session, url_for, current_app
6
+ )
7
+ from werkzeug.security import check_password_hash, generate_password_hash
8
+
9
+ from AstroSpace.db import get_conn
10
+
11
+ bp = Blueprint('auth', __name__, url_prefix='/auth')
12
+
13
+ @bp.route('/register', methods=('GET', 'POST'))
14
+ def register():
15
+ if request.method == 'POST':
16
+ username = request.form['username'].lower()
17
+ password = request.form['password']
18
+ db = get_conn()
19
+ error = None
20
+
21
+ if not username:
22
+ error = 'Username is required.'
23
+ elif not password:
24
+ error = 'Password is required.'
25
+
26
+ if error is None:
27
+ try:
28
+ with db.cursor() as cur:
29
+ cur.execute("LOCK TABLE users IN EXCLUSIVE MODE")
30
+ cur.execute("SELECT COUNT(*) AS user_count FROM users")
31
+ user_count = cur.fetchone()["user_count"]
32
+
33
+ if user_count >= current_app.config['MAX_USERS']:
34
+ error = 'Sorry maximum number of users reached :('
35
+ else:
36
+ is_first_user = user_count == 0
37
+ cur.execute(
38
+ "INSERT INTO users (username, password, admin) VALUES (%s, %s, %s)",
39
+ (username, generate_password_hash(password), is_first_user),
40
+ )
41
+ if error is None:
42
+ db.commit()
43
+ return redirect(url_for("auth.login"))
44
+ db.rollback()
45
+ except IntegrityError:
46
+ db.rollback()
47
+ error = f"User {username} is already registered."
48
+
49
+ flash(error)
50
+
51
+ return render_template('auth/auth.html', title='Register', WebName = current_app.config["TITLE"])
52
+
53
+ @bp.route('/login', methods=('GET', 'POST'))
54
+ def login():
55
+ if request.method == 'POST':
56
+ username = request.form['username'].lower()
57
+ password = request.form['password']
58
+ db = get_conn()
59
+ error = None
60
+ with db.cursor() as cur:
61
+ cur.execute(
62
+ "SELECT * FROM users WHERE username = %s", (username,) )
63
+ user = cur.fetchone()
64
+
65
+ if user is None:
66
+ error = 'Incorrect username.'
67
+ elif not check_password_hash(user['password'], password):
68
+ error = 'Incorrect password.'
69
+
70
+ if error is None:
71
+ session.clear()
72
+ session['user_id'] = user['id']
73
+ return redirect(url_for('index'))
74
+
75
+ flash(error)
76
+
77
+ return render_template('auth/auth.html', title='Log In', WebName = current_app.config["TITLE"])
78
+
79
+ @bp.before_app_request
80
+ def load_logged_in_user():
81
+ user_id = session.get('user_id')
82
+
83
+ if user_id is None:
84
+ g.user = None
85
+ else:
86
+ cur = get_conn().cursor()
87
+ cur.execute(
88
+ "SELECT * FROM users WHERE id = %s", (user_id,)
89
+ )
90
+ g.user = cur.fetchone()
91
+
92
+ @bp.route('/logout', methods=('POST',))
93
+ def logout():
94
+ session.clear()
95
+ return redirect(url_for('index'))
96
+
97
+ def login_required(view):
98
+ @functools.wraps(view)
99
+ def wrapped_view(**kwargs):
100
+ if g.user is None:
101
+ return redirect(url_for('auth.login'))
102
+
103
+ return view(**kwargs)
104
+
105
+ return wrapped_view