Skip to content

Commit 69dd4eb

Browse files
authored
Merge branch 'develop' into celery_logging_fix
2 parents 4d1e697 + 3c7e1a4 commit 69dd4eb

File tree

8 files changed

+272
-68
lines changed

8 files changed

+272
-68
lines changed

CHANGES.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
## What's Changed
22

3+
1.3.0 (2024-12-19)
4+
==================
5+
6+
Web Application
7+
~~~~~~~~~~~~~~~
8+
* Exclude source-specific WFSS files from observation page by @bhilbert4 in https://github.com/spacetelescope/jwql/pull/1651
9+
* Switch URL for prog info scraping to use the OPO site by @bhilbert4 in https://github.com/spacetelescope/jwql/pull/1662
10+
11+
Project & API Documentation
12+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
* Added logging configuration to config file, and use it when opening logging by @york-stsci in https://github.com/spacetelescope/jwql/pull/1635
14+
* Fix bad parens in dark monitor model definitions by @bhilbert4 in https://github.com/spacetelescope/jwql/pull/1644
15+
* Add radius keyword to bokeh.figure.circle calls by @bhilbert4 in https://github.com/spacetelescope/jwql/pull/1643
16+
* Remove bokeh templating code by @bhilbert4 in https://github.com/spacetelescope/jwql/pull/1647
17+
* Update Bad Pixel Monitor to use Django DB Models by @mfixstsci in https://github.com/spacetelescope/jwql/pull/1497
18+
* Update Bias Monitor to use Django DB Models by @bsunnquist in https://github.com/spacetelescope/jwql/pull/1503
19+
20+
321
1.2.11 (2024-08-26)
422
===================
523

jwql/jwql_monitors/monitor_filesystem.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,27 @@
4949
import numpy as np
5050
from sqlalchemy.exc import DataError
5151

52-
from jwql.database.database_interface import engine
53-
from jwql.database.database_interface import session
54-
from jwql.database.database_interface import FilesystemCharacteristics
55-
from jwql.database.database_interface import FilesystemGeneral
56-
from jwql.database.database_interface import FilesystemInstrument
57-
from jwql.database.database_interface import CentralStore
5852
from jwql.utils.logging_functions import log_info, log_fail
5953
from jwql.utils.permissions import set_permissions
6054
from jwql.utils.constants import FILESYSTEM_MONITOR_SUBDIRS, FILE_SUFFIX_TYPES, FILTERS_PER_INSTRUMENT, INSTRUMENT_SERVICE_MATCH
6155
from jwql.utils.constants import JWST_INSTRUMENT_NAMES, JWST_INSTRUMENT_NAMES_MIXEDCASE, JWST_INSTRUMENT_NAMES_MIXEDCASE
56+
from jwql.utils.constants import ON_GITHUB_ACTIONS, ON_READTHEDOCS
6257
from jwql.utils.utils import filename_parser
6358
from jwql.utils.utils import get_config
6459
from jwql.utils.monitor_utils import initialize_instrument_monitor, update_monitor_table
6560
from jwql.utils.protect_module import lock_module
6661
from jwql.website.apps.jwql.data_containers import get_instrument_proposals
6762

63+
if not ON_GITHUB_ACTIONS and not ON_READTHEDOCS:
64+
# Need to set up django apps before we can access the models
65+
import django # noqa: E402 (module level import not at top of file)
66+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jwql.website.jwql_proj.settings")
67+
django.setup()
68+
69+
# Import * is okay here because this module specifically only contains database models
70+
# for this monitor
71+
from jwql.website.apps.jwql.monitor_models.common import * # noqa: E402 (module level import not at top of file)
72+
6873
SETTINGS = get_config()
6974
FILESYSTEM = SETTINGS['filesystem']
7075
PROPRIETARY_FILESYSTEM = os.path.join(FILESYSTEM, 'proprietary')
@@ -74,6 +79,7 @@
7479
PREVIEW_IMAGES = SETTINGS['preview_image_filesystem']
7580
THUMBNAILS = SETTINGS['thumbnail_filesystem']
7681
LOGS = SETTINGS['log_dir']
82+
WORKING = SETTINGS['working']
7783

7884

7985
def files_per_filter():
@@ -232,7 +238,8 @@ def get_area_stats(central_storage_dict):
232238
'logs': LOGS,
233239
'preview_images': PREVIEW_IMAGES,
234240
'thumbnails': THUMBNAILS,
235-
'all': CENTRAL}
241+
'all': CENTRAL,
242+
'working':WORKING}
236243

237244
counteddirs = []
238245

@@ -368,7 +375,7 @@ def initialize_results_dicts():
368375
A dictionary for the ``central_storage`` database table
369376
"""
370377

371-
now = datetime.datetime.now()
378+
now = datetime.datetime.now(datetime.timezone.utc)
372379

373380
general_results_dict = {}
374381
general_results_dict['date'] = now
@@ -430,9 +437,9 @@ def update_central_store_database(central_storage_dict):
430437
new_record['size'] = central_storage_dict[area]['size']
431438
new_record['used'] = central_storage_dict[area]['used']
432439
new_record['available'] = central_storage_dict[area]['available']
433-
with engine.begin() as connection:
434-
connection.execute(CentralStore.__table__.insert(), new_record)
435-
session.close()
440+
441+
entry = CentralStorage(**new_record)
442+
entry.save()
436443

437444

438445
def update_characteristics_database(char_info):
@@ -447,7 +454,7 @@ def update_characteristics_database(char_info):
447454
using that filter/pupil.
448455
"""
449456
logging.info('\tUpdating the characteristics database')
450-
now = datetime.datetime.now()
457+
now = datetime.datetime.now(datetime.timezone.utc)
451458

452459
# Add data to filesystem_instrument table
453460
for instrument in ['nircam', 'niriss', 'nirspec', 'miri']:
@@ -458,11 +465,9 @@ def update_characteristics_database(char_info):
458465
new_record['instrument'] = instrument
459466
new_record['filter_pupil'] = optics
460467
new_record['obs_per_filter_pupil'] = values
461-
with engine.begin() as connection:
462-
connection.execute(
463-
FilesystemCharacteristics.__table__.insert(), new_record)
464468

465-
session.close()
469+
entry = FilesystemCharacteristics(**new_record)
470+
entry.save()
466471

467472

468473
def update_database(general_results_dict, instrument_results_dict, central_storage_dict):
@@ -478,8 +483,8 @@ def update_database(general_results_dict, instrument_results_dict, central_stora
478483
"""
479484
logging.info('\tUpdating the database')
480485

481-
with engine.begin() as connection:
482-
connection.execute(FilesystemGeneral.__table__.insert(), general_results_dict)
486+
fs_general_entry = FilesystemGeneral(**general_results_dict)
487+
fs_general_entry.save()
483488

484489
# Add data to filesystem_instrument table
485490
for instrument in JWST_INSTRUMENT_NAMES:
@@ -493,13 +498,8 @@ def update_database(general_results_dict, instrument_results_dict, central_stora
493498

494499
# Protect against updated enum options that have not been propagated to
495500
# the table definition
496-
try:
497-
with engine.begin() as connection:
498-
connection.execute(FilesystemInstrument.__table__.insert(), new_record)
499-
except DataError as e:
500-
logging.error(e)
501-
502-
session.close()
501+
fs_instrument_entry = FilesystemInstrument(**new_record)
502+
fs_instrument_entry.save()
503503

504504

505505
@lock_module
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#! /usr/bin/env python
2+
3+
"""Tests for the ``archive_database_update`` module.
4+
5+
Authors
6+
-------
7+
8+
- Bryan Hilbert
9+
10+
Use
11+
---
12+
13+
These tests can be run via the command line (omit the ``-s`` to
14+
suppress verbose output to stdout):
15+
::
16+
17+
pytest -s test_archive_database_update.py
18+
"""
19+
20+
21+
import pytest
22+
23+
from jwql.website.apps.jwql import archive_database_update
24+
25+
26+
def test_filter_rootnames():
27+
"""Test the filtering of source-based level 2 files
28+
"""
29+
files = ['jw06434-c1021_s000001510_nircam_f444w-grismr.fits',
30+
'jw01068004001_02102_00001_nrcb4_rate.fits',
31+
'jw06434-c1021_t000_nircam_clear-f090w_segm.fits',
32+
'jw06434-o001_t000_nircam_clear-f090w_segm.fits',
33+
'jw02183117001_03103_00001-seg001_nrca1_rate.fits']
34+
35+
filtered = archive_database_update.filter_rootnames(files)
36+
expected = ['jw01068004001_02102_00001_nrcb4_rate.fits',
37+
'jw02183117001_03103_00001-seg001_nrca1_rate.fits']
38+
assert filtered == expected

jwql/utils/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@
381381
FILE_PROG_ID_LEN = 5
382382
FILE_SEG_LEN = 3
383383
FILE_SOURCE_ID_LEN = 5
384+
FILE_SOURCE_ID_LONG_LEN = 9
384385
FILE_TARG_ID_LEN = 3
385386
FILE_VISIT_GRP_LEN = 2
386387
FILE_VISIT_LEN = 3

jwql/website/apps/jwql/archive_database_update.py

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,45 @@
4343
import logging
4444
import os
4545
import argparse
46+
import re
4647

4748
import numpy as np
4849
import django
4950

5051
from django.apps import apps
5152
from jwql.utils.protect_module import lock_module
52-
from jwql.utils.constants import DEFAULT_MODEL_CHARFIELD
53-
54-
# These lines are needed in order to use the Django models in a standalone
55-
# script (as opposed to code run as a result of a webpage request). If these
56-
# lines are not run, the script will crash when attempting to import the
57-
# Django models in the line below.
58-
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jwql.website.jwql_proj.settings")
59-
django.setup()
60-
61-
from jwql.website.apps.jwql.models import Archive, Observation, Proposal, RootFileInfo # noqa
62-
from jwql.utils.constants import JWST_INSTRUMENT_NAMES_MIXEDCASE # noqa
63-
from jwql.utils.logging_functions import log_info, log_fail # noqa
64-
from jwql.utils.monitor_utils import initialize_instrument_monitor # noqa
65-
from jwql.utils.constants import MAST_QUERY_LIMIT # noqa
66-
from jwql.utils.utils import filename_parser, filesystem_path, get_config # noqa
67-
from jwql.website.apps.jwql.data_containers import create_archived_proposals_context # noqa
68-
from jwql.website.apps.jwql.data_containers import get_instrument_proposals, get_filenames_by_instrument # noqa
69-
from jwql.website.apps.jwql.data_containers import get_proposal_info, mast_query_filenames_by_instrument, mast_query_by_rootname # noqa
70-
71-
FILESYSTEM = get_config()['filesystem']
53+
from jwql.utils.constants import (DEFAULT_MODEL_CHARFIELD,
54+
FILE_PROG_ID_LEN,
55+
FILE_AC_O_ID_LEN,
56+
FILE_AC_CAR_ID_LEN,
57+
FILE_SOURCE_ID_LONG_LEN,
58+
FILE_TARG_ID_LEN,
59+
JWST_INSTRUMENT_NAMES_MIXEDCASE,
60+
MAST_QUERY_LIMIT,
61+
ON_GITHUB_ACTIONS,
62+
ON_READTHEDOCS
63+
)
64+
from jwql.utils.logging_functions import log_info, log_fail
65+
from jwql.utils.monitor_utils import initialize_instrument_monitor
66+
from jwql.utils.utils import filename_parser, filesystem_path, get_config
67+
from jwql.website.apps.jwql.data_containers import create_archived_proposals_context
68+
from jwql.website.apps.jwql.data_containers import get_instrument_proposals, get_filenames_by_instrument
69+
from jwql.website.apps.jwql.data_containers import (get_proposal_info,
70+
mast_query_filenames_by_instrument,
71+
mast_query_by_rootname
72+
)
73+
74+
75+
if not ON_GITHUB_ACTIONS and not ON_READTHEDOCS:
76+
# These lines are needed in order to use the Django models in a standalone
77+
# script (as opposed to code run as a result of a webpage request). If these
78+
# lines are not run, the script will crash when attempting to import the
79+
# Django models in the line below.
80+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jwql.website.jwql_proj.settings")
81+
django.setup()
82+
83+
from jwql.website.apps.jwql.models import Archive, Observation, Proposal, RootFileInfo # noqa
84+
FILESYSTEM = get_config()['filesystem']
7285

7386

7487
@log_info
@@ -113,6 +126,11 @@ def get_updates(update_database):
113126

114127
# Get set of unique rootnames
115128
all_rootnames = set(['_'.join(f.split('/')[-1].split('_')[:-1]) for f in filenames])
129+
130+
# Filter source-based level 2 files out of the rootnames and filenames
131+
all_rootnames = filter_rootnames(all_rootnames)
132+
filenames = filter_filenames(filenames, all_rootnames)
133+
116134
rootnames = []
117135
for rootname in all_rootnames:
118136
filename_dict = filename_parser(rootname)
@@ -510,6 +528,64 @@ def fill_empty_rootfileinfo(rootfileinfo_set):
510528
logging.info(f'\tSaved {saved_rootfileinfos} Root File Infos')
511529

512530

531+
def filter_filenames(fnames, roots):
532+
"""Filter out filenames from ``fnames`` that don't match the names in ``roots``
533+
534+
Parameters
535+
----------
536+
fnames : list
537+
List of filenames
538+
539+
roots : list
540+
List of rootnames
541+
542+
Returns
543+
-------
544+
filtered_fnames : list
545+
Filtered list of filenames
546+
"""
547+
filtered_fnames = []
548+
for fname in fnames:
549+
for root in roots:
550+
if root in fname:
551+
filtered_fnames.append(fname)
552+
break
553+
return filtered_fnames
554+
555+
556+
def filter_rootnames(rootnames):
557+
"""Filter out rootnames that we know can't be parsed by the filename_parser. We use this
558+
custom filter here rather than within the filename parser itself because in archive_database_update
559+
we can end up providing thousands of unrecognized filenames (e.g. source-based WFSS files) to
560+
the filename parser, which would result in thousands of logging statments and massive log files.
561+
This way, we filter out the rootnames that obviously won't be parsed before calling the
562+
filename_parser with the rest. jw06434-c1021_s000001510_nircam_f444w-grismr
563+
jw06434-c1021_t000_nircam_clear-f090w_segm.fits
564+
565+
Parameters
566+
----------
567+
rootnames : list
568+
List of rootnames
569+
570+
Returns
571+
-------
572+
good_rootnames : list
573+
List of rootnames that do not match the filters
574+
"""
575+
stage_2_source = \
576+
r"jw" \
577+
r"(?P<program_id>\d{" + f"{FILE_PROG_ID_LEN}" + "})"\
578+
r"-(?P<ac_id>(o\d{" + f"{FILE_AC_O_ID_LEN}" + r"}|(c|a|r)\d{" + f"{FILE_AC_CAR_ID_LEN}" + "}))"\
579+
r"_(?P<target_id>(s\d{" + f"{FILE_SOURCE_ID_LONG_LEN}" + r"}|(t)\d{" + f"{FILE_TARG_ID_LEN}" + "}))"\
580+
r"_(?P<instrument>(nircam|niriss|miri))"\
581+
r"_(?P<optical_elements>((?!_)[\w-])+)"\
582+
r"-"
583+
584+
elements = re.compile(stage_2_source)
585+
good_rootnames = [e for e in rootnames if elements.match(e) is None]
586+
return good_rootnames
587+
588+
513589
@lock_module
514590
def protected_code(update_database, fill_empty_list):
515591
"""Protected code ensures only 1 instance of module will run at any given time

jwql/website/apps/jwql/data_containers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,12 +2057,15 @@ def text_scrape(prop_id):
20572057
program_meta : dict
20582058
Dictionary containing information about program
20592059
"""
2060+
# Ensure prop_id is a 5-digit string
2061+
prop_id = str(prop_id).zfill(5)
20602062

20612063
# Generate url
2062-
url = 'http://www.stsci.edu/cgi-bin/get-proposal-info?id=' + str(prop_id) + '&submit=Go&observatory=JWST'
2064+
url = f'https://www.stsci.edu/jwst-program-info/program/?program={prop_id}'
20632065
html = BeautifulSoup(requests.get(url).text, 'lxml')
20642066
not_available = "not available via this interface" in html.text
20652067
not_available |= "temporarily unable" in html.text
2068+
not_available |= "internal error" in html.text
20662069

20672070
program_meta = {}
20682071
program_meta['prop_id'] = prop_id
@@ -2081,7 +2084,7 @@ def text_scrape(prop_id):
20812084

20822085
links = html.findAll('a')
20832086

2084-
proposal_type = links[0].contents[0]
2087+
proposal_type = links[3].contents[0]
20852088

20862089
program_meta['prop_type'] = proposal_type
20872090

0 commit comments

Comments
 (0)