diff --git a/.github/workflows/annotationengine.yml b/.github/workflows/annotationengine.yml index 3d738f3..5708e4e 100644 --- a/.github/workflows/annotationengine.yml +++ b/.github/workflows/annotationengine.yml @@ -34,10 +34,10 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - uses: actions/cache@v4 with: path: ~/.cache/pip @@ -48,7 +48,7 @@ jobs: with: auto-update-conda: true auto-activate-base: true - python-version: 3.9 + python-version: "3.10" - name: Install dependencies shell: bash -l {0} run: | diff --git a/annotationengine/api.py b/annotationengine/api.py index 3d13fc4..93a1310 100644 --- a/annotationengine/api.py +++ b/annotationengine/api.py @@ -16,6 +16,8 @@ from caveclient.auth import AuthClient import werkzeug import traceback +import datetime +import pytz from annotationengine.aligned_volume import ( get_aligned_volumes, @@ -100,6 +102,14 @@ def handle_invalid_usage(error): location="args", help="whether to only return valid items", ) +query_parser.add_argument( + "timestamp", + type=str, + default=None, + location="args", + required=False, + help="timestamp for filtering results (will default to now if not required)", +) def check_aligned_volume(aligned_volume): @@ -243,11 +253,26 @@ def put(self, aligned_volume_name: str) -> FullMetadataSchema: @api_bp.expect(query_parser) def get(self, aligned_volume_name: str): """Get list of annotation tables for a aligned_volume""" + args = query_parser.parse_args() + timestamp_str = args.get("timestamp", None) + if timestamp_str is None: + timestamp = datetime.datetime.now(datetime.timezone.utc) + else: + # fromisoformat accepts common ISO 8601 formats with or without microseconds + # (e.g. 2026-02-19T10:30:00 or 2026-02-19T10:30:00.123456) + timestamp = datetime.datetime.fromisoformat( + timestamp_str.replace("Z", "+00:00") + ) + if timestamp.tzinfo is None: + timestamp = pytz.utc.localize(timestamp) + else: + timestamp = timestamp.astimezone(pytz.utc) + check_aligned_volume(aligned_volume_name) db = get_db(aligned_volume_name) - args = query_parser.parse_args() tables = db.database._get_existing_table_names( - filter_valid=args.get("filter_valid", True) + filter_valid=args.get("filter_valid", True), + filter_timestamp=timestamp ) return tables, 200 diff --git a/dev.Dockerfile b/dev.Dockerfile index 313ebb3..5174f3a 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -4,8 +4,8 @@ RUN git config --global http.sslVerify false && \ mkdir -p /home/nginx/.cloudvolume/secrets && chown -R nginx /home/nginx && usermod -d /home/nginx -s /bin/bash nginx RUN python -m pip install --upgrade pip -COPY dev_requirements.txt /app/. -RUN pip install -r dev_requirements.txt +COPY requirements.txt /app/. +RUN pip install -r requirements.txt COPY timeout.conf /etc/nginx/conf.d/ COPY . /app \ No newline at end of file diff --git a/dev_requirements.txt b/dev_requirements.txt index 23557dc..3fe547c 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -31,7 +31,7 @@ deprecated==1.2.13 # via redis dynamicannotationdb==5.2.2 # via -r requirements.in -emannotationschemas==5.1.1 +emannotationschemas==5.24.12 # via # -r requirements.in # dynamicannotationdb @@ -178,7 +178,7 @@ requests==2.27.1 # -r requirements.in # caveclient # middle-auth-client -shapely==1.8.0 +shapely==2.0.3 # via # dynamicannotationdb # emannotationschemas diff --git a/requirements.in b/requirements.in index 80d34ca..1386111 100644 --- a/requirements.in +++ b/requirements.in @@ -1,6 +1,6 @@ Flask<2.0 emannotationschemas>=5.24.13 -dynamicannotationdb>=5.5.1 +dynamicannotationdb>=5.14.1 caveclient>=4.20.2 multiwrapper jsonschema<4.0 diff --git a/requirements.txt b/requirements.txt index ed7a8f6..2b31758 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,9 +27,7 @@ click==7.1.2 # via flask decorator==5.1.1 # via ipython -deprecated==1.2.13 - # via redis -dynamicannotationdb==5.8.1 +dynamicannotationdb==5.14.1 # via -r requirements.in emannotationschemas==5.24.13 # via @@ -121,9 +119,7 @@ numpy==1.21.5 orderedmultidict==1.0.1 # via furl packaging==21.3 - # via - # geoalchemy2 - # redis + # via geoalchemy2 pandas==1.3.5 # via caveclient parso==0.8.3 @@ -153,8 +149,6 @@ pytz==2021.3 # dynamicannotationdb # flask-restx # pandas -redis==4.1.3 - # via middle-auth-client requests==2.27.1 # via # -r requirements.in @@ -200,7 +194,5 @@ werkzeug==1.0.1 # flask # flask-accepts # flask-restx -wrapt==1.13.3 - # via deprecated wtforms==3.0.1 # via flask-admin diff --git a/test/conftest.py b/test/conftest.py index 75a00cd..b7b1260 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -58,7 +58,8 @@ def test_aligned_volume(): @pytest.fixture(scope="module") def client(): - flask_app = create_app(config_name="testing") + config_name = os.environ.get("FLASK_CONFIGURATION", "testing") + flask_app = create_app(config_name=config_name) test_logger.info("Starting test flask app...") # Create a test client using the Flask application configured for testing @@ -66,8 +67,23 @@ def client(): # Establish an application context with flask_app.app_context(): db.create_all() + logging.info("yielding testing_client") yield testing_client + # --- Cleanup Phase --- + logging.info("Tearing down testing_client, committing session") + + db.session.commit() + + logging.info("dropping all tables") + # 2. Drop the tables db.drop_all() + logging.info("removing session") + # 1. Remove the scoped session to prevent "ResourceBusy" errors + db.session.remove() + logging.info("disposing engine") + # 3. Dispose of the engine to kill the connection pool (The Fix) + db.engine.dispose() + logging.info("cleanup complete") @pytest.fixture(scope="module")