**Commit Message:**
Enhance app with active pgroup handling and token updates Added active pgroup state management across the app for user-specific settings. Improved token handling with decoding, saving user data, and setting OpenAPI authorization. Updated components, API calls, and forms to support dynamic pgroup selection and user-specific features.
This commit is contained in:
parent
4630bcfac5
commit
6cde57f783
@ -110,36 +110,56 @@ contacts = [
|
||||
return_addresses = [
|
||||
Address(
|
||||
id=1,
|
||||
street="123 Hobbiton St",
|
||||
city="Shire",
|
||||
pgroups="p20000, p20002",
|
||||
status="active",
|
||||
house_number="123",
|
||||
street="Hobbiton St",
|
||||
city="Hobbitbourg",
|
||||
state="Shire",
|
||||
zipcode="12345",
|
||||
country="Middle Earth",
|
||||
),
|
||||
Address(
|
||||
id=2,
|
||||
street="456 Rohan Rd",
|
||||
pgroups="p20000, p20001",
|
||||
status="active",
|
||||
house_number="456",
|
||||
street="Rohan Rd",
|
||||
city="Edoras",
|
||||
state="Rohan",
|
||||
zipcode="67890",
|
||||
country="Middle Earth",
|
||||
),
|
||||
Address(
|
||||
id=3,
|
||||
street="789 Greenwood Dr",
|
||||
pgroups="p20001, p20002",
|
||||
status="active",
|
||||
house_number="789",
|
||||
street="Greenwood Dr",
|
||||
city="Mirkwood",
|
||||
state="Greenwood",
|
||||
zipcode="13579",
|
||||
country="Middle Earth",
|
||||
),
|
||||
Address(
|
||||
id=4,
|
||||
street="321 Gondor Ave",
|
||||
pgroups="p20001, p20002, p20003",
|
||||
status="active",
|
||||
house_number="321",
|
||||
street="Gondor Ave",
|
||||
city="Minas Tirith",
|
||||
state="Gondor",
|
||||
zipcode="24680",
|
||||
country="Middle Earth",
|
||||
),
|
||||
Address(
|
||||
id=5,
|
||||
street="654 Falgorn Pass",
|
||||
pgroups="p20004, p20005",
|
||||
status="active",
|
||||
house_number="654",
|
||||
street="Falgorn Pass",
|
||||
city="Rivendell",
|
||||
state="Rivendell",
|
||||
zipcode="11223",
|
||||
country="Middle Earth",
|
||||
),
|
||||
@ -234,11 +254,11 @@ dewars = [
|
||||
|
||||
# Define proposals
|
||||
proposals = [
|
||||
Proposal(id=1, number="p20000"),
|
||||
Proposal(id=2, number="p20001"),
|
||||
Proposal(id=3, number="p20002"),
|
||||
Proposal(id=4, number="p20003"),
|
||||
Proposal(id=5, number="p20004"),
|
||||
Proposal(id=1, number="202400125"),
|
||||
Proposal(id=2, number="202400235"),
|
||||
Proposal(id=3, number="202400237"),
|
||||
Proposal(id=4, number="202400336"),
|
||||
Proposal(id=5, number="202400255"),
|
||||
]
|
||||
|
||||
# Define shipment specific dewars
|
||||
|
@ -46,10 +46,14 @@ class Address(Base):
|
||||
__tablename__ = "addresses"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
street = Column(String(255))
|
||||
city = Column(String(255))
|
||||
zipcode = Column(String(255))
|
||||
country = Column(String(255))
|
||||
status = Column(String(255), default="active")
|
||||
pgroups = Column(String(255), nullable=False)
|
||||
street = Column(String(255), nullable=False)
|
||||
house_number = Column(String(255), nullable=True)
|
||||
city = Column(String(255), nullable=False)
|
||||
state = Column(String(255), nullable=True)
|
||||
zipcode = Column(String(255), nullable=False)
|
||||
country = Column(String(255), nullable=False)
|
||||
|
||||
shipments = relationship("Shipment", back_populates="return_address")
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
from .address import router as address_router
|
||||
from .address import protected_router as address_router
|
||||
from .contact import router as contact_router
|
||||
from .proposal import router as proposal_router
|
||||
from .dewar import router as dewar_router
|
||||
from .shipment import router as shipment_router
|
||||
from .auth import router as auth_router
|
||||
from .protected_router import protected_router as protected_router
|
||||
|
||||
__all__ = [
|
||||
"address_router",
|
||||
@ -12,4 +13,5 @@ __all__ = [
|
||||
"dewar_router",
|
||||
"shipment_router",
|
||||
"auth_router",
|
||||
"protected_router",
|
||||
]
|
||||
|
@ -1,20 +1,63 @@
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from fastapi import Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_
|
||||
from typing import List
|
||||
from app.schemas import Address as AddressSchema, AddressCreate, AddressUpdate
|
||||
|
||||
from app.routers.auth import get_current_user
|
||||
from app.schemas import (
|
||||
Address as AddressSchema,
|
||||
AddressCreate,
|
||||
AddressUpdate,
|
||||
loginData,
|
||||
)
|
||||
from app.models import Address as AddressModel
|
||||
from app.dependencies import get_db
|
||||
|
||||
router = APIRouter()
|
||||
from app.routers.protected_router import protected_router
|
||||
|
||||
|
||||
@router.get("/", response_model=List[AddressSchema])
|
||||
async def get_return_addresses(db: Session = Depends(get_db)):
|
||||
return db.query(AddressModel).all()
|
||||
@protected_router.get("/", response_model=List[AddressSchema])
|
||||
async def get_return_addresses(
|
||||
active_pgroup: str = Query(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: loginData = Depends(get_current_user),
|
||||
):
|
||||
if active_pgroup not in current_user.pgroups:
|
||||
raise HTTPException(status_code=400, detail="Invalid pgroup provided.")
|
||||
|
||||
# Return only active addresses
|
||||
user_addresses = (
|
||||
db.query(AddressModel)
|
||||
.filter(
|
||||
AddressModel.pgroups.like(f"%{active_pgroup}%"),
|
||||
AddressModel.status == "active",
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return user_addresses
|
||||
|
||||
|
||||
@router.post("/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED)
|
||||
@protected_router.get("/all", response_model=List[AddressSchema])
|
||||
async def get_all_addresses(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: loginData = Depends(get_current_user),
|
||||
):
|
||||
# Fetch all active addresses associated with the user's pgroups
|
||||
user_pgroups = current_user.pgroups
|
||||
filters = [AddressModel.pgroups.like(f"%{pgroup}%") for pgroup in user_pgroups]
|
||||
user_addresses = (
|
||||
db.query(AddressModel)
|
||||
.filter(AddressModel.status == "active", or_(*filters))
|
||||
.all()
|
||||
)
|
||||
return user_addresses
|
||||
|
||||
|
||||
@protected_router.post(
|
||||
"/", response_model=AddressSchema, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_return_address(address: AddressCreate, db: Session = Depends(get_db)):
|
||||
print("Payload received by backend:", address.dict()) # Log incoming payload
|
||||
|
||||
if db.query(AddressModel).filter(AddressModel.city == address.city).first():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@ -22,10 +65,14 @@ async def create_return_address(address: AddressCreate, db: Session = Depends(ge
|
||||
)
|
||||
|
||||
db_address = AddressModel(
|
||||
pgroups=address.pgroups,
|
||||
house_number=address.house_number,
|
||||
street=address.street,
|
||||
city=address.city,
|
||||
state=address.state,
|
||||
zipcode=address.zipcode,
|
||||
country=address.country,
|
||||
status="active",
|
||||
)
|
||||
|
||||
db.add(db_address)
|
||||
@ -34,29 +81,74 @@ async def create_return_address(address: AddressCreate, db: Session = Depends(ge
|
||||
return db_address
|
||||
|
||||
|
||||
@router.put("/{address_id}", response_model=AddressSchema)
|
||||
@protected_router.put("/{address_id}", response_model=AddressSchema)
|
||||
async def update_return_address(
|
||||
address_id: int, address: AddressUpdate, db: Session = Depends(get_db)
|
||||
):
|
||||
# Retrieve the existing address
|
||||
db_address = db.query(AddressModel).filter(AddressModel.id == address_id).first()
|
||||
if not db_address:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Address not found."
|
||||
)
|
||||
for key, value in address.dict(exclude_unset=True).items():
|
||||
setattr(db_address, key, value)
|
||||
|
||||
# Normalize existing and new pgroups (remove whitespace, handle case
|
||||
# sensitivity if needed)
|
||||
existing_pgroups = (
|
||||
set(p.strip() for p in db_address.pgroups.split(",") if p.strip())
|
||||
if db_address.pgroups
|
||||
else set()
|
||||
)
|
||||
new_pgroups = (
|
||||
set(p.strip() for p in address.pgroups.split(",") if p.strip())
|
||||
if address.pgroups
|
||||
else set()
|
||||
)
|
||||
|
||||
# Check if any old pgroups are being removed (strict validation against removal)
|
||||
if not new_pgroups.issuperset(existing_pgroups):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Modifying pgroups to remove existing ones is not allowed.",
|
||||
)
|
||||
|
||||
# Combine existing and new pgroups (only additions are allowed)
|
||||
combined_pgroups = existing_pgroups.union(new_pgroups)
|
||||
|
||||
# Mark the current address as obsolete
|
||||
db_address.status = "inactive"
|
||||
db.commit()
|
||||
db.refresh(db_address)
|
||||
return db_address
|
||||
|
||||
# Create a new address with updated values and the combined pgroups
|
||||
new_address = AddressModel(
|
||||
pgroups=",".join(combined_pgroups), # Join set back into comma-separated string
|
||||
house_number=address.house_number or db_address.house_number,
|
||||
street=address.street or db_address.street,
|
||||
city=address.city or db_address.city,
|
||||
state=address.state or db_address.state,
|
||||
zipcode=address.zipcode or db_address.zipcode,
|
||||
country=address.country or db_address.country,
|
||||
status="active", # Newly created address will be active
|
||||
)
|
||||
|
||||
# Save the new address
|
||||
db.add(new_address)
|
||||
db.commit()
|
||||
db.refresh(new_address)
|
||||
|
||||
return new_address
|
||||
|
||||
|
||||
@router.delete("/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@protected_router.delete("/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_return_address(address_id: int, db: Session = Depends(get_db)):
|
||||
db_address = db.query(AddressModel).filter(AddressModel.id == address_id).first()
|
||||
if not db_address:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Address not found."
|
||||
)
|
||||
db.delete(db_address)
|
||||
|
||||
# Mark the address as obsolete instead of deleting it
|
||||
db_address.status = "inactive"
|
||||
db.commit()
|
||||
return
|
||||
|
@ -14,8 +14,13 @@ mock_users_db = {
|
||||
"testuser": {
|
||||
"username": "testuser",
|
||||
"password": "testpass", # In a real scenario, store the hash of the password
|
||||
"pgroups": [20000, 20001, 20003],
|
||||
}
|
||||
"pgroups": ["p20000", "p20001", "p20002", "p20003"],
|
||||
},
|
||||
"testuser2": {
|
||||
"username": "testuser2",
|
||||
"password": "testpass2", # In a real scenario, store the hash of the password
|
||||
"pgroups": ["p20004", "p20005", "p20006"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -39,30 +44,17 @@ def create_access_token(data: dict) -> str:
|
||||
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)) -> loginData:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
token_expired_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token expired",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
pgroups = payload.get("pgroups")
|
||||
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = loginData(username=username, pgroups=pgroups)
|
||||
print(f"[DEBUG] Username decoded from token: {username}") # Add debug log here
|
||||
return loginData(username=username, pgroups=payload.get("pgroups"))
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise token_expired_exception
|
||||
print("[DEBUG] Token expired")
|
||||
raise HTTPException(status_code=401, detail="Token expired")
|
||||
except jwt.InvalidTokenError:
|
||||
raise credentials_exception
|
||||
|
||||
return token_data
|
||||
print("[DEBUG] Invalid token")
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
|
||||
@router.post("/token/login", response_model=loginToken)
|
||||
|
7
backend/app/routers/protected_router.py
Normal file
7
backend/app/routers/protected_router.py
Normal file
@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.routers.auth import get_current_user
|
||||
|
||||
protected_router = APIRouter(
|
||||
dependencies=[Depends(get_current_user)] # Applies to all routes
|
||||
)
|
@ -7,7 +7,6 @@ import shutil
|
||||
from app.schemas import (
|
||||
Puck as PuckSchema,
|
||||
Sample as SampleSchema,
|
||||
SampleEventResponse,
|
||||
SampleEventCreate,
|
||||
Sample,
|
||||
)
|
||||
@ -90,106 +89,69 @@ async def create_sample_event(
|
||||
return sample # Return the sample, now including `mount_count`
|
||||
|
||||
|
||||
# Route to fetch the last (most recent) sample event
|
||||
@router.get("/samples/{sample_id}/events/last", response_model=SampleEventResponse)
|
||||
async def get_last_sample_event(sample_id: int, db: Session = Depends(get_db)):
|
||||
# Ensure the sample exists
|
||||
sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first()
|
||||
if not sample:
|
||||
raise HTTPException(status_code=404, detail="Sample not found")
|
||||
|
||||
# Get the most recent event for the sample
|
||||
last_event = (
|
||||
db.query(SampleEventModel)
|
||||
.filter(SampleEventModel.sample_id == sample_id)
|
||||
.order_by(SampleEventModel.timestamp.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
if not last_event:
|
||||
raise HTTPException(status_code=404, detail="No events found for the sample")
|
||||
|
||||
return SampleEventResponse(
|
||||
id=last_event.id,
|
||||
sample_id=last_event.sample_id,
|
||||
event_type=last_event.event_type,
|
||||
timestamp=last_event.timestamp,
|
||||
) # Response will automatically use the SampleEventResponse schema
|
||||
|
||||
|
||||
@router.post("/samples/{sample_id}/upload-images")
|
||||
async def upload_sample_images(
|
||||
sample_id: int,
|
||||
uploaded_files: List[UploadFile] = File(...), # Accept multiple files
|
||||
uploaded_files: list[UploadFile] = File(...),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Uploads images for a sample and stores them in a directory structure:
|
||||
images/user/date/dewar_name/puck_name/position/.
|
||||
logging.info(f"Received files: {[file.filename for file in uploaded_files]}")
|
||||
|
||||
"""
|
||||
Uploads images for a given sample and saves them to a directory structure.
|
||||
Args:
|
||||
sample_id (int): ID of the sample.
|
||||
uploaded_files (List[UploadFile]): List of image files to be uploaded.
|
||||
db (Session): SQLAlchemy database session.
|
||||
uploaded_files (list[UploadFile]): A list of files uploaded with the request.
|
||||
db (Session): Database session.
|
||||
"""
|
||||
# Fetch sample details from the database
|
||||
|
||||
# 1. Validate Sample
|
||||
sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first()
|
||||
if not sample:
|
||||
raise HTTPException(status_code=404, detail="Sample not found")
|
||||
|
||||
# Retrieve associated dewar_name, puck_name and position
|
||||
puck = sample.puck
|
||||
if not puck:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"No puck associated with sample ID {sample_id}"
|
||||
)
|
||||
|
||||
dewar_name = puck.dewar.dewar_name if puck.dewar else None
|
||||
if not dewar_name:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"No dewar associated with puck ID {puck.id}"
|
||||
)
|
||||
|
||||
puck_name = puck.puck_name
|
||||
position = sample.position
|
||||
|
||||
# Retrieve username (hardcoded for now—can be fetched dynamically if needed)
|
||||
username = "e16371"
|
||||
|
||||
# Today's date in the format YYYY-MM-DD
|
||||
# 2. Define Directory Structure
|
||||
username = "e16371" # Hardcoded username; replace with dynamic logic if applicable
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Generate the directory path based on the structure
|
||||
base_dir = (
|
||||
Path("images") / username / today / dewar_name / puck_name / str(position)
|
||||
dewar_name = (
|
||||
sample.puck.dewar.dewar_name
|
||||
if sample.puck and sample.puck.dewar
|
||||
else "default_dewar"
|
||||
)
|
||||
|
||||
# Create directories if they don't exist
|
||||
puck_name = sample.puck.puck_name if sample.puck else "default_puck"
|
||||
position = sample.position if sample.position else "default_position"
|
||||
base_dir = Path(f"images/{username}/{today}/{dewar_name}/{puck_name}/{position}")
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Save each uploaded image to the directory
|
||||
# 3. Process and Save Each File
|
||||
saved_files = []
|
||||
for file in uploaded_files:
|
||||
# Validate file content type
|
||||
# Validate MIME type
|
||||
if not file.content_type.startswith("image/"):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid file type: {file.filename}. Must be an image.",
|
||||
detail=f"Invalid file type: {file.filename}. Only images are accepted.",
|
||||
)
|
||||
|
||||
# Create a file path for storing the uploaded file
|
||||
# Save file to the base directory
|
||||
file_path = base_dir / file.filename
|
||||
|
||||
# Save the file from the file stream
|
||||
try:
|
||||
# Save the file
|
||||
with file_path.open("wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
saved_files.append(str(file_path)) # Track saved file paths
|
||||
except Exception as e:
|
||||
logging.error(f"Error saving file {file.filename}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error saving file {file.filename}: {str(e)}",
|
||||
detail=f"Could not save file {file.filename}."
|
||||
f" Ensure the server has correct permissions.",
|
||||
)
|
||||
|
||||
# 4. Return Saved Files Information
|
||||
logging.info(f"Uploaded {len(saved_files)} files for sample {sample_id}.")
|
||||
return {
|
||||
"message": f"{len(uploaded_files)} images uploaded successfully.",
|
||||
"path": str(base_dir), # Return the base directory for reference
|
||||
"message": f"{len(saved_files)} images uploaded successfully.",
|
||||
"files": saved_files,
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class loginToken(BaseModel):
|
||||
|
||||
class loginData(BaseModel):
|
||||
username: str
|
||||
pgroups: List[int]
|
||||
pgroups: List[str]
|
||||
|
||||
|
||||
class DewarTypeBase(BaseModel):
|
||||
@ -392,22 +392,29 @@ class ContactPersonUpdate(BaseModel):
|
||||
|
||||
|
||||
class AddressCreate(BaseModel):
|
||||
pgroups: str
|
||||
house_number: Optional[str] = None
|
||||
street: str
|
||||
city: str
|
||||
state: Optional[str] = None
|
||||
zipcode: str
|
||||
country: str
|
||||
|
||||
|
||||
class Address(AddressCreate):
|
||||
id: int
|
||||
status: str = "active"
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AddressUpdate(BaseModel):
|
||||
pgroups: str
|
||||
house_number: Optional[str] = None
|
||||
street: Optional[str] = None
|
||||
city: Optional[str] = None
|
||||
state: Optional[str] = None
|
||||
zipcode: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
|
||||
|
@ -18,6 +18,7 @@ from app.routers import (
|
||||
sample,
|
||||
)
|
||||
from app.database import Base, engine, SessionLocal
|
||||
from app.routers.protected_router import protected_router
|
||||
|
||||
|
||||
# Utility function to fetch metadata from pyproject.toml
|
||||
@ -139,8 +140,8 @@ def on_startup():
|
||||
load_slots_data(db)
|
||||
else: # dev or test environments
|
||||
print(f"{environment.capitalize()} environment: Regenerating database.")
|
||||
# Base.metadata.drop_all(bind=engine)
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
if environment == "dev":
|
||||
from app.database import load_sample_data
|
||||
|
||||
@ -154,9 +155,10 @@ def on_startup():
|
||||
|
||||
|
||||
# Include routers with correct configuration
|
||||
app.include_router(protected_router, prefix="/protected", tags=["protected"])
|
||||
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
app.include_router(contact.router, prefix="/contacts", tags=["contacts"])
|
||||
app.include_router(address.router, prefix="/addresses", tags=["addresses"])
|
||||
app.include_router(address.protected_router, prefix="/addresses", tags=["addresses"])
|
||||
app.include_router(proposal.router, prefix="/proposals", tags=["proposals"])
|
||||
app.include_router(dewar.router, prefix="/dewars", tags=["dewars"])
|
||||
app.include_router(shipment.router, prefix="/shipments", tags=["shipments"])
|
||||
|
BIN
backend/tests/sample_image/IMG_1942.jpg
Normal file
BIN
backend/tests/sample_image/IMG_1942.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 576 KiB |
27
frontend/package-lock.json
generated
27
frontend/package-lock.json
generated
@ -29,6 +29,7 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.15.0",
|
||||
@ -485,6 +486,15 @@
|
||||
"@devexpress/dx-core": "4.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@devexpress/dx-scheduler-core/node_modules/rrule": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.7.1.tgz",
|
||||
"integrity": "sha512-4p20u/1U7WqR3Nb1hOUrm0u1nSI7sO93ZUVZEZ5HeF6Gr5OlJuyhwEGRvUHq8ZfrPsq5gfa5b9dqnUs/kPqpIw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||
@ -4732,6 +4742,15 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -5992,10 +6011,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rrule": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.7.1.tgz",
|
||||
"integrity": "sha512-4p20u/1U7WqR3Nb1hOUrm0u1nSI7sO93ZUVZEZ5HeF6Gr5OlJuyhwEGRvUHq8ZfrPsq5gfa5b9dqnUs/kPqpIw==",
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz",
|
||||
"integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.15.0",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { setUpToken, clearToken } from './utils/auth'; // Import the token utilities
|
||||
|
||||
import ResponsiveAppBar from './components/ResponsiveAppBar';
|
||||
import ShipmentView from './pages/ShipmentView';
|
||||
@ -16,38 +17,77 @@ const App: React.FC = () => {
|
||||
const [openAddressManager, setOpenAddressManager] = useState(false);
|
||||
const [openContactsManager, setOpenContactsManager] = useState(false);
|
||||
|
||||
const handleOpenAddressManager = () => {
|
||||
setOpenAddressManager(true);
|
||||
};
|
||||
const handleOpenAddressManager = () => setOpenAddressManager(true);
|
||||
const handleCloseAddressManager = () => setOpenAddressManager(false);
|
||||
const handleOpenContactsManager = () => setOpenContactsManager(true);
|
||||
const handleCloseContactsManager = () => setOpenContactsManager(false);
|
||||
|
||||
const handleCloseAddressManager = () => {
|
||||
setOpenAddressManager(false);
|
||||
};
|
||||
const [pgroups, setPgroups] = useState<string[]>([]);
|
||||
const [activePgroup, setActivePgroup] = useState<string>('');
|
||||
|
||||
const handleOpenContactsManager = () => {
|
||||
setOpenContactsManager(true);
|
||||
};
|
||||
// On app load, configure the token
|
||||
useEffect(() => {
|
||||
setUpToken(); // Ensure token is loaded into OpenAPI on app initialization
|
||||
}, []);
|
||||
|
||||
const handleCloseContactsManager = () => {
|
||||
setOpenContactsManager(false);
|
||||
useEffect(() => {
|
||||
const updateStateFromLocalStorage = () => {
|
||||
const user = localStorage.getItem('user');
|
||||
console.log("[DEBUG] User data in localStorage (update):", user); // Debug
|
||||
if (user) {
|
||||
try {
|
||||
const parsedUser = JSON.parse(user);
|
||||
if (parsedUser.pgroups && Array.isArray(parsedUser.pgroups)) {
|
||||
setPgroups(parsedUser.pgroups);
|
||||
setActivePgroup(parsedUser.pgroups[0] || '');
|
||||
console.log("[DEBUG] Pgroups updated in state:", parsedUser.pgroups);
|
||||
} else {
|
||||
console.warn("[DEBUG] No pgroups found in user data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[DEBUG] Error parsing user data:", error);
|
||||
}
|
||||
} else {
|
||||
console.warn("[DEBUG] No user in localStorage");
|
||||
}
|
||||
};
|
||||
|
||||
// Run on component mount
|
||||
updateStateFromLocalStorage();
|
||||
|
||||
// Listen for localStorage changes
|
||||
window.addEventListener('storage', updateStateFromLocalStorage);
|
||||
|
||||
// Cleanup listener on unmount
|
||||
return () => {
|
||||
window.removeEventListener('storage', updateStateFromLocalStorage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePgroupChange = (newPgroup: string) => {
|
||||
setActivePgroup(newPgroup);
|
||||
console.log(`pgroup changed to: ${newPgroup}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<ResponsiveAppBar
|
||||
activePgroup={activePgroup}
|
||||
onOpenAddressManager={handleOpenAddressManager}
|
||||
onOpenContactsManager={handleOpenContactsManager}
|
||||
pgroups={pgroups || []} // Default to an empty array
|
||||
currentPgroup={activePgroup}
|
||||
onPgroupChange={handlePgroupChange}
|
||||
/>
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginView />} />
|
||||
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
|
||||
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView />} />} />
|
||||
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView activePgroup={activePgroup} />} />} />
|
||||
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
|
||||
<Route path="/results" element={<ProtectedRoute element={<ResultsView />} />} />
|
||||
{/* Other routes as necessary */}
|
||||
</Routes>
|
||||
<Modal open={openAddressManager} onClose={handleCloseAddressManager} title="Address Management">
|
||||
<AddressManager />
|
||||
<AddressManager pgroups={pgroups} activePgroup={activePgroup} />
|
||||
</Modal>
|
||||
<Modal open={openContactsManager} onClose={handleCloseContactsManager} title="Contacts Management">
|
||||
<ContactsManager />
|
||||
|
@ -10,7 +10,7 @@ interface ModalProps {
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ open, onClose, title, children }) => {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} fullWidth maxWidth="md">
|
||||
<Dialog open={open} onClose={onClose} fullWidth maxWidth="lg">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
{children}
|
||||
|
@ -7,25 +7,43 @@ import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import Container from '@mui/material/Container';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { Button } from '@mui/material';
|
||||
import { Button, Select, FormControl, InputLabel, } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import logo from '../assets/icons/psi_01_sn.svg';
|
||||
import '../App.css';
|
||||
import { clearToken } from '../utils/auth';
|
||||
|
||||
|
||||
interface ResponsiveAppBarProps {
|
||||
activePgroup: string;
|
||||
onOpenAddressManager: () => void;
|
||||
onOpenContactsManager: () => void;
|
||||
pgroups: string[]; // Pass the pgroups from the server
|
||||
currentPgroup: string; // Currently selected pgroup
|
||||
onPgroupChange: (pgroup: string) => void; // Callback when selected pgroup changes
|
||||
}
|
||||
|
||||
const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManager, onOpenContactsManager }) => {
|
||||
const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({
|
||||
activePgroup,
|
||||
onOpenAddressManager,
|
||||
onOpenContactsManager,
|
||||
pgroups,
|
||||
currentPgroup,
|
||||
onPgroupChange }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
|
||||
const [selectedPgroup, setSelectedPgroup] = useState(currentPgroup);
|
||||
console.log('Active Pgroup:', activePgroup);
|
||||
const handlePgroupChange = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
const newPgroup = event.target.value as string;
|
||||
setSelectedPgroup(newPgroup);
|
||||
onPgroupChange(newPgroup); // Inform parent about the change
|
||||
};
|
||||
|
||||
const pages = [
|
||||
{ name: 'Home', path: '/' },
|
||||
@ -59,8 +77,8 @@ const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManage
|
||||
|
||||
const handleLogout = () => {
|
||||
console.log("Performing logout...");
|
||||
localStorage.removeItem('token');
|
||||
navigate('/login');
|
||||
clearToken(); // Clear the token from localStorage and OpenAPI
|
||||
navigate('/login'); // Redirect to login page
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (action: string) => {
|
||||
@ -123,7 +141,31 @@ const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManage
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<FormControl variant="outlined" size="small">
|
||||
<InputLabel sx={{ color: 'white' }}>pgroup</InputLabel>
|
||||
<Select
|
||||
value={selectedPgroup}
|
||||
onChange={handlePgroupChange}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderColor: 'white',
|
||||
minWidth: 150,
|
||||
'&:focus': { borderColor: '#f0db4f' },
|
||||
}}
|
||||
>
|
||||
{pgroups && pgroups.length > 0 ? (
|
||||
pgroups.map((pgroup) => (
|
||||
<MenuItem key={pgroup} value={pgroup}>
|
||||
{pgroup}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>No pgroups available</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title="Open settings">
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
|
@ -12,6 +12,7 @@ import DewarDetails from './DewarDetails';
|
||||
const MAX_COMMENTS_LENGTH = 200;
|
||||
|
||||
interface ShipmentDetailsProps {
|
||||
activePgroup: string;
|
||||
isCreatingShipment: boolean;
|
||||
sx?: SxProps;
|
||||
selectedShipment: Shipment | null;
|
||||
@ -23,6 +24,7 @@ interface ShipmentDetailsProps {
|
||||
}
|
||||
|
||||
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
activePgroup,
|
||||
sx,
|
||||
selectedShipment,
|
||||
setSelectedDewar,
|
||||
@ -34,6 +36,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
const [comments, setComments] = useState<string>(selectedShipment?.comments || '');
|
||||
const [initialComments, setInitialComments] = useState<string>(selectedShipment?.comments || '');
|
||||
|
||||
console.log('Active Pgroup:', activePgroup); // Debugging or use it where required
|
||||
|
||||
const initialNewDewarState: Partial<Dewar> = {
|
||||
dewar_name: '',
|
||||
tracking_number: '',
|
||||
|
@ -6,15 +6,18 @@ import {
|
||||
import { SelectChangeEvent } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import {
|
||||
ContactPersonCreate, ContactPerson, Address, Proposal, ContactsService, AddressesService, AddressCreate, ProposalsService,
|
||||
ContactPersonCreate, ContactPerson, Address, AddressCreate, Proposal, ContactsService, AddressesService, ProposalsService,
|
||||
OpenAPI, ShipmentCreate, ShipmentsService
|
||||
} from '../../openapi';
|
||||
import { useEffect } from 'react';
|
||||
import { CountryList } from './CountryList'; // Import the list of countries
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
|
||||
|
||||
const MAX_COMMENTS_LENGTH = 200;
|
||||
|
||||
interface ShipmentFormProps {
|
||||
activePgroup: string;
|
||||
sx?: SxProps;
|
||||
onCancel: () => void;
|
||||
refreshShipments: () => void;
|
||||
@ -26,7 +29,17 @@ const fuse = new Fuse(CountryList, {
|
||||
includeScore: true,
|
||||
});
|
||||
|
||||
const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshShipments }) => {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token) {
|
||||
OpenAPI.TOKEN = token; // Ensure OpenAPI client uses this token
|
||||
}
|
||||
|
||||
const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
activePgroup,
|
||||
sx = {},
|
||||
onCancel,
|
||||
refreshShipments }) => {
|
||||
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
|
||||
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
|
||||
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
|
||||
@ -37,7 +50,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
firstname: '', lastname: '', phone_number: '', email: ''
|
||||
});
|
||||
const [newReturnAddress, setNewReturnAddress] = React.useState<Omit<Address, 'id'>>({
|
||||
street: '', city: '', zipcode: '', country: ''
|
||||
pgroup:'', house_number: '', street: '', city: '', state: '', zipcode: '', country: ''
|
||||
});
|
||||
const [newShipment, setNewShipment] = React.useState<Partial<ShipmentCreate>>({
|
||||
shipment_name: '', shipment_status: 'In preparation', comments: ''
|
||||
@ -80,12 +93,23 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
};
|
||||
|
||||
const getAddresses = async () => {
|
||||
if (!activePgroup) {
|
||||
console.error("Active pgroup is missing.");
|
||||
setErrorMessage("Active pgroup is missing. Unable to load addresses.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Pass activePgroup directly as a string (not as an object)
|
||||
const fetchedAddresses: Address[] =
|
||||
await AddressesService.getReturnAddressesAddressesGet();
|
||||
await AddressesService.getReturnAddressesAddressesGet(activePgroup);
|
||||
|
||||
setReturnAddresses(fetchedAddresses);
|
||||
} catch {
|
||||
setErrorMessage('Failed to load return addresses.');
|
||||
} catch (error) {
|
||||
console.error("Error fetching addresses:", error);
|
||||
|
||||
// Extract and log meaningful information from OpenAPI errors (if available)
|
||||
setErrorMessage("Failed to load return addresses due to API error.");
|
||||
}
|
||||
};
|
||||
|
||||
@ -102,7 +126,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
getContacts();
|
||||
getAddresses();
|
||||
getProposals();
|
||||
}, []);
|
||||
}, [activePgroup]);
|
||||
|
||||
const handleCountryInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
@ -170,13 +194,14 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
|
||||
const payload: ShipmentCreate = {
|
||||
shipment_name: newShipment.shipment_name || '',
|
||||
shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required at all
|
||||
shipment_date: new Date().toISOString().split('T')[0], // Remove if date is not required
|
||||
shipment_status: newShipment.shipment_status || 'In preparation',
|
||||
comments: newShipment.comments || '',
|
||||
contact_person_id: selectedContactPersonId!,
|
||||
return_address_id: selectedReturnAddressId!,
|
||||
proposal_id: selectedProposalId!,
|
||||
dewars: newShipment.dewars || []
|
||||
dewars: newShipment.dewars || [],
|
||||
//pgroup: activePgroup,
|
||||
};
|
||||
|
||||
console.log('Shipment Payload being sent:', payload);
|
||||
@ -249,32 +274,44 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
};
|
||||
|
||||
const handleSaveNewReturnAddress = async () => {
|
||||
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city ||
|
||||
!newReturnAddress.country) {
|
||||
// Validate address form data
|
||||
if (!validateZipCode(newReturnAddress.zipcode) || !newReturnAddress.street || !newReturnAddress.city || !newReturnAddress.country) {
|
||||
setErrorMessage('Please fill in all new return address fields correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure activePgroup is available
|
||||
if (!activePgroup) {
|
||||
setErrorMessage('Active pgroup is missing. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct the payload
|
||||
const payload: AddressCreate = {
|
||||
pgroups: activePgroup, // Use the activePgroup prop directly
|
||||
house_number: newReturnAddress.house_number,
|
||||
street: newReturnAddress.street,
|
||||
city: newReturnAddress.city,
|
||||
state: newReturnAddress.state,
|
||||
zipcode: newReturnAddress.zipcode,
|
||||
country: newReturnAddress.country,
|
||||
};
|
||||
|
||||
console.log('Return Address Payload being sent:', payload);
|
||||
|
||||
// Call the API with the completed payload
|
||||
try {
|
||||
const response: Address = await AddressesService.createReturnAddressAddressesPost(payload);
|
||||
setReturnAddresses([...returnAddresses, response]);
|
||||
setReturnAddresses([...returnAddresses, response]); // Update the address state
|
||||
setErrorMessage(null);
|
||||
setSelectedReturnAddressId(response.id);
|
||||
setSelectedReturnAddressId(response.id); // Set the newly created address ID to the form
|
||||
} catch (error) {
|
||||
console.error('Failed to create a new return address:', error);
|
||||
setErrorMessage('Failed to create a new return address. Please try again later.');
|
||||
}
|
||||
|
||||
setNewReturnAddress({ street: '', city: '', zipcode: '', country: '' });
|
||||
// Reset form inputs and close the "Create New Address" form
|
||||
setNewReturnAddress({ pgroup: '', house_number: '', street: '', city: '', state: '', zipcode: '', country: '' });
|
||||
setIsCreatingReturnAddress(false);
|
||||
};
|
||||
|
||||
@ -390,11 +427,22 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
onChange={handleReturnAddressChange}
|
||||
displayEmpty
|
||||
>
|
||||
{returnAddresses.map((address) => (
|
||||
<MenuItem key={address.id} value={address.id.toString()}>
|
||||
{`${address.street}, ${address.city}, ${address.zipcode}, ${address.country}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
{returnAddresses.map((address) => {
|
||||
const addressParts = [
|
||||
address.house_number,
|
||||
address.street,
|
||||
address.city,
|
||||
address.zipcode,
|
||||
address.state,
|
||||
address.country
|
||||
].filter(part => part); // Remove falsy (null/undefined/empty) values
|
||||
|
||||
return (
|
||||
<MenuItem key={address.id} value={address.id.toString()}>
|
||||
{addressParts.join(', ')} {/* Join the valid address parts with a comma */}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
<MenuItem value="new">
|
||||
<em>Create New Return Address</em>
|
||||
</MenuItem>
|
||||
@ -402,6 +450,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
</FormControl>
|
||||
{isCreatingReturnAddress && (
|
||||
<>
|
||||
|
||||
<TextField
|
||||
label="Street"
|
||||
name="street"
|
||||
@ -410,6 +459,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Number"
|
||||
name="number"
|
||||
value={newReturnAddress.house_number}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, house_number: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
label="City"
|
||||
name="city"
|
||||
@ -418,6 +474,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="State"
|
||||
name="state"
|
||||
value={newReturnAddress.state}
|
||||
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, state: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
label="Zip Code"
|
||||
name="zipcode"
|
||||
|
@ -11,6 +11,7 @@ import bottleRed from '/src/assets/icons/bottle-svgrepo-com-red.svg';
|
||||
|
||||
interface ShipmentPanelProps {
|
||||
setCreatingShipment: (value: boolean) => void;
|
||||
activePgroup: string;
|
||||
selectShipment: (shipment: Shipment | null) => void;
|
||||
selectedShipment: Shipment | null;
|
||||
sx?: SxProps;
|
||||
@ -27,6 +28,7 @@ const statusIconMap: Record<string, string> = {
|
||||
};
|
||||
|
||||
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
activePgroup,
|
||||
setCreatingShipment,
|
||||
selectShipment,
|
||||
selectedShipment,
|
||||
@ -37,6 +39,8 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
}) => {
|
||||
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
||||
|
||||
console.log('Active Pgroup:', activePgroup);
|
||||
|
||||
const handleDeleteShipment = async () => {
|
||||
if (selectedShipment) {
|
||||
const confirmDelete = window.confirm(
|
||||
|
@ -2,26 +2,57 @@ import React from 'react';
|
||||
import Fuse from 'fuse.js';
|
||||
import { CountryList } from '../components/CountryList';
|
||||
import {
|
||||
Container, Typography, List, ListItem, IconButton, TextField, Box, ListItemText, ListItemSecondaryAction, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button
|
||||
Container,
|
||||
Typography,
|
||||
List,
|
||||
ListItem,
|
||||
IconButton,
|
||||
TextField,
|
||||
Box,
|
||||
ListItemText,
|
||||
ListItemSecondaryAction,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Button,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { AddressesService } from '../../openapi';
|
||||
import type { Address, AddressCreate, AddressUpdate } from '../models/Address';
|
||||
import {Address, AddressCreate, AddressesService, AddressUpdate} from '../../openapi';
|
||||
|
||||
|
||||
interface AddressManagerProps {
|
||||
pgroups: string[];
|
||||
activePgroup: string;
|
||||
}
|
||||
|
||||
|
||||
// Extend the generated Address type
|
||||
interface AddressWithPgroups extends Address {
|
||||
associatedPgroups: string[]; // Dynamically added pgroups
|
||||
}
|
||||
|
||||
const fuse = new Fuse(CountryList, {
|
||||
threshold: 0.3, // Lower threshold for stricter matches
|
||||
includeScore: true,
|
||||
});
|
||||
|
||||
const AddressManager: React.FC = () => {
|
||||
const AddressManager: React.FC<AddressManagerProps> = ({ pgroups, activePgroup }) => {
|
||||
// Use pgroups and activePgroup directly
|
||||
console.log('User pgroups:', pgroups);
|
||||
console.log('Active pgroup:', activePgroup);
|
||||
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
|
||||
const [addresses, setAddresses] = React.useState<Address[]>([]);
|
||||
const [newAddress, setNewAddress] = React.useState<Partial<Address>>({
|
||||
house_number: '',
|
||||
street: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zipcode: '',
|
||||
country: '',
|
||||
});
|
||||
@ -45,18 +76,28 @@ const AddressManager: React.FC = () => {
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchAddresses = async () => {
|
||||
const fetchAllData = async () => {
|
||||
try {
|
||||
const response = await AddressesService.getReturnAddressesAddressesGet();
|
||||
setAddresses(response);
|
||||
const response = await AddressesService.getAllAddressesAddressesAllGet();
|
||||
|
||||
// Preprocess: Add associated and unassociated pgroups
|
||||
const transformedAddresses = response.map((address) => {
|
||||
const addressPgroups = address.pgroups?.split(',').map((p) => p.trim()) || [];
|
||||
const associatedPgroups = pgroups.filter((pgroup) => addressPgroups.includes(pgroup));
|
||||
return {
|
||||
...address,
|
||||
associatedPgroups, // pgroups linked to the address
|
||||
};
|
||||
});
|
||||
|
||||
setAddresses(transformedAddresses);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch addresses', error);
|
||||
setErrorMessage('Failed to load addresses. Please try again later.');
|
||||
setErrorMessage('Failed to load addresses. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
fetchAddresses();
|
||||
}, []);
|
||||
fetchAllData();
|
||||
}, [pgroups]);
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = event.target;
|
||||
@ -66,30 +107,40 @@ const AddressManager: React.FC = () => {
|
||||
const handleAddOrUpdateAddress = async () => {
|
||||
try {
|
||||
if (editAddressId !== null) {
|
||||
// Update address
|
||||
await AddressesService.updateReturnAddressAddressesAddressIdPut(editAddressId, newAddress as AddressUpdate);
|
||||
setAddresses(addresses.map(address => address.id === editAddressId ? { ...address, ...newAddress } : address));
|
||||
// Update address (mark old one obsolete, create a new one)
|
||||
const updatedAddress = await AddressesService.updateReturnAddressAddressesAddressIdPut(
|
||||
editAddressId,
|
||||
newAddress as AddressUpdate
|
||||
);
|
||||
|
||||
// Replace old address with the new one in the list
|
||||
setAddresses(addresses.map(address =>
|
||||
address.id === editAddressId ? updatedAddress : address
|
||||
).filter(address => address.status === "active")); // Keep only active addresses
|
||||
setEditAddressId(null);
|
||||
} else {
|
||||
// Add new address
|
||||
const response = await AddressesService.createReturnAddressAddressesPost(newAddress as AddressCreate);
|
||||
setAddresses([...addresses, response]);
|
||||
}
|
||||
setNewAddress({ street: '', city: '', zipcode: '', country: '' });
|
||||
setNewAddress({ house_number:'', street: '', city: '', state: '', zipcode: '', country: '' });
|
||||
setErrorMessage(null);
|
||||
} catch (error) {
|
||||
console.error('Failed to add/update address', error);
|
||||
setErrorMessage('Failed to add/update address. Please try again later.');
|
||||
setErrorMessage('Failed to add/update address. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAddress = async (id: number) => {
|
||||
try {
|
||||
// Delete (inactivate) the address
|
||||
await AddressesService.deleteReturnAddressAddressesAddressIdDelete(id);
|
||||
setAddresses(addresses.filter(address => address.id !== id));
|
||||
|
||||
// Remove the obsolete address from the active list in the UI
|
||||
setAddresses(addresses.filter(address => address.id !== id && address.status === "active"));
|
||||
} catch (error) {
|
||||
console.error('Failed to delete address', error);
|
||||
setErrorMessage('Failed to delete address. Please try again later.');
|
||||
setErrorMessage('Failed to delete address. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
@ -115,52 +166,149 @@ const AddressManager: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const togglePgroupAssociation = async (addressId: number, pgroup: string) => {
|
||||
try {
|
||||
const address = addresses.find((addr) => addr.id === addressId);
|
||||
if (!address) return;
|
||||
|
||||
const isAssociated = address.associatedPgroups.includes(pgroup);
|
||||
|
||||
// Only allow adding a pgroup
|
||||
if (isAssociated) {
|
||||
console.warn('Removing a pgroup is not allowed.');
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedPgroups = [...address.associatedPgroups, pgroup]; // Add the pgroup
|
||||
|
||||
// Update the backend
|
||||
await AddressesService.updateReturnAddressAddressesAddressIdPut(addressId, {
|
||||
...address,
|
||||
pgroups: updatedPgroups.join(','), // Sync updated pgroups
|
||||
});
|
||||
|
||||
// Update address in local state
|
||||
setAddresses((prevAddresses) =>
|
||||
prevAddresses.map((addr) =>
|
||||
addr.id === addressId
|
||||
? { ...addr, associatedPgroups: updatedPgroups }
|
||||
: addr
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to add pgroup association', error);
|
||||
setErrorMessage('Failed to add pgroup association. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const renderPgroupChips = (address: AddressWithPgroups) => {
|
||||
return pgroups.map((pgroup) => {
|
||||
const isAssociated = address.associatedPgroups.includes(pgroup);
|
||||
return (
|
||||
<Chip
|
||||
key={pgroup}
|
||||
label={pgroup}
|
||||
onClick={
|
||||
!isAssociated // Only allow adding a new pgroup, no removal
|
||||
? () => togglePgroupAssociation(address.id, pgroup)
|
||||
: undefined
|
||||
}
|
||||
sx={{
|
||||
backgroundColor: isAssociated ? '#19d238' : '#b0b0b0',
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 'bold',
|
||||
height: '20px',
|
||||
fontSize: '12px',
|
||||
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)',
|
||||
cursor: isAssociated ? 'default' : 'pointer', // Disable pointer for associated chips
|
||||
'&:hover': { opacity: isAssociated ? 1 : 0.8 }, // Disable hover effect for associated chips
|
||||
mr: 1,
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Addresses Management
|
||||
</Typography>
|
||||
<Box display="flex" justifyContent="center" alignItems="center" mb={3}>
|
||||
<TextField label="Street" name="street" value={newAddress.street || ''} onChange={handleInputChange} />
|
||||
<TextField label="City" name="city" value={newAddress.city || ''} onChange={handleInputChange} />
|
||||
<TextField label="Zipcode" name="zipcode" value={newAddress.zipcode || ''} onChange={handleInputChange} />
|
||||
<Box display="flex" justifyContent="center" alignItems="center" mb={3} gap={2}>
|
||||
<TextField
|
||||
label="Country"
|
||||
name="country"
|
||||
value={newAddress.country || ''}
|
||||
onChange={handleCountryInputChange}
|
||||
fullWidth
|
||||
required
|
||||
error={
|
||||
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
|
||||
} // Show an error only if in add/edit mode and country is empty
|
||||
helperText={
|
||||
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
|
||||
? 'Country is required'
|
||||
: ''
|
||||
}
|
||||
label="pgroup"
|
||||
name="pgroup"
|
||||
value={newAddress.activePgroup || ''}
|
||||
disabled
|
||||
sx={{ width: '120px' }} // Small fixed-size for non-editable field
|
||||
/>
|
||||
|
||||
{/* Render suggestions dynamically */}
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<TextField
|
||||
label="Number"
|
||||
name="house_number"
|
||||
value={newAddress.house_number || ''}
|
||||
onChange={handleInputChange}
|
||||
sx={{ width: '100px' }} // Small size for Number field
|
||||
/>
|
||||
<TextField
|
||||
label="Street"
|
||||
name="street"
|
||||
value={newAddress.street || ''}
|
||||
onChange={handleInputChange}
|
||||
sx={{ flex: 1 }} // Street field takes the most space
|
||||
/>
|
||||
<TextField
|
||||
label="City"
|
||||
name="city"
|
||||
value={newAddress.city || ''}
|
||||
onChange={handleInputChange}
|
||||
sx={{ width: '150px' }} // Medium size for City
|
||||
/>
|
||||
<TextField
|
||||
label="State"
|
||||
name="state"
|
||||
value={newAddress.state || ''}
|
||||
onChange={handleInputChange}
|
||||
sx={{ width: '100px' }} // Small size
|
||||
/>
|
||||
<TextField
|
||||
label="Zipcode"
|
||||
name="zipcode"
|
||||
value={newAddress.zipcode || ''}
|
||||
onChange={handleInputChange}
|
||||
sx={{ width: '120px' }} // Medium size for Zipcode
|
||||
/>
|
||||
<Box sx={{ position: 'relative', flex: 1 }}> {/* Country field dynamically takes available space */}
|
||||
<TextField
|
||||
label="Country"
|
||||
name="country"
|
||||
value={newAddress.country || ''}
|
||||
onChange={handleCountryInputChange}
|
||||
fullWidth
|
||||
required
|
||||
error={
|
||||
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
|
||||
}
|
||||
helperText={
|
||||
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
|
||||
? 'Country is required'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Render suggestions dynamically */}
|
||||
{countrySuggestions.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
background: 'white',
|
||||
marginTop: '4px', /* Add space below the input */
|
||||
position: 'absolute',
|
||||
width: '100%', /* Match the TextField width */
|
||||
zIndex: 10, /* Ensure it is above other UI elements */
|
||||
top: '100%', // Place below the TextField
|
||||
left: 0,
|
||||
zIndex: 10,
|
||||
width: '100%', // Match the width of the TextField
|
||||
marginTop: '4px', // Small spacing below the TextField
|
||||
}}
|
||||
>
|
||||
{countrySuggestions.map((suggestion, index) => (
|
||||
@ -172,7 +320,6 @@ const AddressManager: React.FC = () => {
|
||||
'&:hover': { background: '#f5f5f5' },
|
||||
}}
|
||||
onClick={() => {
|
||||
// Update country field with the clicked suggestion
|
||||
setNewAddress({ ...newAddress, country: suggestion });
|
||||
setCountrySuggestions([]); // Clear suggestions
|
||||
}}
|
||||
@ -193,14 +340,18 @@ const AddressManager: React.FC = () => {
|
||||
addresses.map((address) => (
|
||||
<ListItem key={address.id} button>
|
||||
<ListItemText
|
||||
primary={`${address.street}, ${address.city}`}
|
||||
secondary={`${address.zipcode} - ${address.country}`}
|
||||
primary={`${address.house_number}, ${address.street}, ${address.city}`}
|
||||
secondary={
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
{renderPgroupChips(address)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton edge="end" color="primary" onClick={() => handleEditAddress(address)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton edge="end" color="secondary" onClick={() => openDialog(address)}>
|
||||
<IconButton edge="end" color="secondary" onClick={() => handleDeleteAddress(address.id)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</ListItemSecondaryAction>
|
||||
|
@ -89,10 +89,25 @@ const LoginView: React.FC = () => {
|
||||
password: password,
|
||||
});
|
||||
|
||||
// Save the token
|
||||
localStorage.setItem('token', response.access_token);
|
||||
navigate('/'); // Redirect post-login
|
||||
OpenAPI.TOKEN = response.access_token;
|
||||
|
||||
// Decode token to extract user data (e.g., pgroups)
|
||||
const decodedToken = JSON.parse(atob(response.access_token.split('.')[1])); // Decode JWT payload
|
||||
const userData = {
|
||||
username: decodedToken.sub,
|
||||
pgroups: decodedToken.pgroups || [], // Ensure pgroups is an array
|
||||
};
|
||||
localStorage.setItem('user', JSON.stringify(userData)); // Save user data in localStorage
|
||||
|
||||
console.log("Token updated successfully:", response.access_token);
|
||||
console.log("User data saved:", userData);
|
||||
|
||||
navigate('/'); // Redirect after successful login
|
||||
} catch (err) {
|
||||
setError('Login failed. Please check your credentials.');
|
||||
console.error("Error during login:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6,14 +6,20 @@ import { Dewar, OpenAPI, Shipment } from '../../openapi';
|
||||
import useShipments from '../hooks/useShipments';
|
||||
import { Grid, Container } from '@mui/material';
|
||||
|
||||
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
|
||||
type ShipmentViewProps = {
|
||||
activePgroup: string;
|
||||
};
|
||||
|
||||
const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
|
||||
const { shipments, error, defaultContactPerson, fetchAndSetShipments } = useShipments();
|
||||
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
||||
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
||||
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAndSetShipments();
|
||||
}, [activePgroup]);
|
||||
|
||||
useEffect(() => {
|
||||
// Detect the current environment
|
||||
const mode = import.meta.env.MODE;
|
||||
@ -52,6 +58,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
if (isCreatingShipment) {
|
||||
return (
|
||||
<ShipmentForm
|
||||
activePgroup={activePgroup}
|
||||
sx={{ flexGrow: 1 }}
|
||||
onCancel={handleCancelShipmentForm}
|
||||
refreshShipments={fetchAndSetShipments}
|
||||
@ -61,6 +68,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
if (selectedShipment) {
|
||||
return (
|
||||
<ShipmentDetails
|
||||
activePgroup={activePgroup}
|
||||
isCreatingShipment={isCreatingShipment}
|
||||
sx={{ flexGrow: 1 }}
|
||||
selectedShipment={selectedShipment}
|
||||
@ -89,6 +97,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
|
||||
}}
|
||||
>
|
||||
<ShipmentPanel
|
||||
activePgroup={activePgroup}
|
||||
setCreatingShipment={setIsCreatingShipment}
|
||||
selectShipment={handleSelectShipment}
|
||||
shipments={shipments}
|
||||
|
16
frontend/src/utils/auth.ts
Normal file
16
frontend/src/utils/auth.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {OpenAPI} from "../../openapi";
|
||||
|
||||
export const setUpToken = (): void => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
OpenAPI.TOKEN = token; // Assign the token to OpenAPI client
|
||||
} else {
|
||||
console.warn("No token found in localStorage.");
|
||||
}
|
||||
};
|
||||
|
||||
export const clearToken = (): void => {
|
||||
localStorage.removeItem('token');
|
||||
OpenAPI.TOKEN = ''; // Clear the token from the OpenAPI client
|
||||
console.log("Token cleared from OpenAPI and localStorage.");
|
||||
};
|
@ -6,20 +6,20 @@
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-01-17T14:03:52.460891Z",
|
||||
"start_time": "2025-01-17T14:03:52.454842Z"
|
||||
"end_time": "2025-01-20T14:44:37.526742Z",
|
||||
"start_time": "2025-01-20T14:44:37.522704Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"import json\n",
|
||||
"\n",
|
||||
"from nbclient.client import timestamp\n",
|
||||
"#from nbclient.client import timestamp\n",
|
||||
"\n",
|
||||
"import backend.aareDBclient as aareDBclient\n",
|
||||
"from aareDBclient.rest import ApiException\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"from app.data.data import sample\n",
|
||||
"#from app.data.data import sample\n",
|
||||
"\n",
|
||||
"#from aareDBclient import SamplesApi, ShipmentsApi, PucksApi\n",
|
||||
"#from aareDBclient.models import SampleEventCreate, SetTellPosition\n",
|
||||
@ -47,13 +47,13 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 40
|
||||
"execution_count": 2
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-01-17T14:13:16.559702Z",
|
||||
"start_time": "2025-01-17T14:13:16.446021Z"
|
||||
"end_time": "2025-01-20T14:44:58.875370Z",
|
||||
"start_time": "2025-01-20T14:44:58.805520Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
@ -152,7 +152,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 44
|
||||
"execution_count": 3
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
@ -456,8 +456,8 @@
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-01-20T11:10:20.017201Z",
|
||||
"start_time": "2025-01-20T11:10:19.953940Z"
|
||||
"end_time": "2025-01-20T14:45:11.812597Z",
|
||||
"start_time": "2025-01-20T14:45:11.793309Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
@ -503,7 +503,9 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Payload being sent to API:\n",
|
||||
"{\"event_type\":\"Mounted\"}\n"
|
||||
"{\"event_type\":\"Mounted\"}\n",
|
||||
"API response:\n",
|
||||
"Sample(id=433, sample_name='Dtpase_1', position=1, puck_id=44, crystalname=None, proteinname=None, positioninpuck=None, priority=1, comments=None, data_collection_parameters=DataCollectionParameters(directory='{sgPuck}/{sgPosition}', oscillation=None, exposure=None, totalrange=None, transmission=None, targetresolution=None, aperture=None, datacollectiontype=None, processingpipeline='', spacegroupnumber=None, cellparameters=None, rescutkey=None, rescutvalue=None, userresolution=None, pdbid='', autoprocfull=False, procfull=False, adpenabled=False, noano=False, ffcscampaign=False, trustedhigh=None, autoprocextraparams=None, chiphiangles=None, dose=None), events=[SampleEventResponse(id=386, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 35, 38)), SampleEventResponse(id=387, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 40, 11)), SampleEventResponse(id=388, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 45, 4)), SampleEventResponse(id=389, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 45, 24)), SampleEventResponse(id=390, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 50, 38)), SampleEventResponse(id=391, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 11, 52, 28)), SampleEventResponse(id=392, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 12, 10, 20)), SampleEventResponse(id=393, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 13, 39, 24)), SampleEventResponse(id=394, sample_id=433, event_type='Mounted', timestamp=datetime.datetime(2025, 1, 20, 15, 45, 12))], mount_count=9, unmount_count=0)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -513,29 +515,9 @@
|
||||
"/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '127.0.0.1'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n",
|
||||
" warnings.warn(\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "ValidationError",
|
||||
"evalue": "3 validation errors for SampleEventResponse\nsample_id\n Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/int_type\nevent_type\n Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/string_type\ntimestamp\n Input should be a valid datetime [type=datetime_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/datetime_type",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
|
||||
"\u001B[0;31mValidationError\u001B[0m Traceback (most recent call last)",
|
||||
"Cell \u001B[0;32mIn[110], line 21\u001B[0m\n\u001B[1;32m 18\u001B[0m \u001B[38;5;28mprint\u001B[39m(sample_event_create\u001B[38;5;241m.\u001B[39mjson()) \u001B[38;5;66;03m# Ensure it matches `SampleEventCreate`\u001B[39;00m\n\u001B[1;32m 20\u001B[0m \u001B[38;5;66;03m# Call the API\u001B[39;00m\n\u001B[0;32m---> 21\u001B[0m api_response \u001B[38;5;241m=\u001B[39m \u001B[43mapi_instance\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcreate_sample_event_samples_samples_sample_id_events_post\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 22\u001B[0m \u001B[43m \u001B[49m\u001B[43msample_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;241;43m433\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;66;43;03m# Ensure this matches a valid sample ID in the database\u001B[39;49;00m\n\u001B[1;32m 23\u001B[0m \u001B[43m \u001B[49m\u001B[43msample_event_create\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msample_event_create\u001B[49m\n\u001B[1;32m 24\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 26\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mAPI response:\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 27\u001B[0m pprint(api_response)\n",
|
||||
"File \u001B[0;32m/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pydantic/validate_call_decorator.py:60\u001B[0m, in \u001B[0;36mvalidate_call.<locals>.validate.<locals>.wrapper_function\u001B[0;34m(*args, **kwargs)\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[38;5;129m@functools\u001B[39m\u001B[38;5;241m.\u001B[39mwraps(function)\n\u001B[1;32m 59\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mwrapper_function\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m---> 60\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mvalidate_call_wrapper\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py:96\u001B[0m, in \u001B[0;36mValidateCallWrapper.__call__\u001B[0;34m(self, *args, **kwargs)\u001B[0m\n\u001B[1;32m 95\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21m__call__\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39margs: Any, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs: Any) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Any:\n\u001B[0;32m---> 96\u001B[0m res \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__pydantic_validator__\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mvalidate_python\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpydantic_core\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mArgsKwargs\u001B[49m\u001B[43m(\u001B[49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 97\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__return_pydantic_validator__:\n\u001B[1;32m 98\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__return_pydantic_validator__(res)\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api/samples_api.py:109\u001B[0m, in \u001B[0;36mSamplesApi.create_sample_event_samples_samples_sample_id_events_post\u001B[0;34m(self, sample_id, sample_event_create, _request_timeout, _request_auth, _content_type, _headers, _host_index)\u001B[0m\n\u001B[1;32m 104\u001B[0m response_data \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mapi_client\u001B[38;5;241m.\u001B[39mcall_api(\n\u001B[1;32m 105\u001B[0m \u001B[38;5;241m*\u001B[39m_param,\n\u001B[1;32m 106\u001B[0m _request_timeout\u001B[38;5;241m=\u001B[39m_request_timeout\n\u001B[1;32m 107\u001B[0m )\n\u001B[1;32m 108\u001B[0m response_data\u001B[38;5;241m.\u001B[39mread()\n\u001B[0;32m--> 109\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mapi_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mresponse_deserialize\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 110\u001B[0m \u001B[43m \u001B[49m\u001B[43mresponse_data\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mresponse_data\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 111\u001B[0m \u001B[43m \u001B[49m\u001B[43mresponse_types_map\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_response_types_map\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 112\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241m.\u001B[39mdata\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:319\u001B[0m, in \u001B[0;36mApiClient.response_deserialize\u001B[0;34m(self, response_data, response_types_map)\u001B[0m\n\u001B[1;32m 317\u001B[0m encoding \u001B[38;5;241m=\u001B[39m match\u001B[38;5;241m.\u001B[39mgroup(\u001B[38;5;241m1\u001B[39m) \u001B[38;5;28;01mif\u001B[39;00m match \u001B[38;5;28;01melse\u001B[39;00m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mutf-8\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 318\u001B[0m response_text \u001B[38;5;241m=\u001B[39m response_data\u001B[38;5;241m.\u001B[39mdata\u001B[38;5;241m.\u001B[39mdecode(encoding)\n\u001B[0;32m--> 319\u001B[0m return_data \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdeserialize\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresponse_text\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mresponse_type\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcontent_type\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 320\u001B[0m \u001B[38;5;28;01mfinally\u001B[39;00m:\n\u001B[1;32m 321\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;241m200\u001B[39m \u001B[38;5;241m<\u001B[39m\u001B[38;5;241m=\u001B[39m response_data\u001B[38;5;241m.\u001B[39mstatus \u001B[38;5;241m<\u001B[39m\u001B[38;5;241m=\u001B[39m \u001B[38;5;241m299\u001B[39m:\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:420\u001B[0m, in \u001B[0;36mApiClient.deserialize\u001B[0;34m(self, response_text, response_type, content_type)\u001B[0m\n\u001B[1;32m 414\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m 415\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m ApiException(\n\u001B[1;32m 416\u001B[0m status\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m0\u001B[39m,\n\u001B[1;32m 417\u001B[0m reason\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUnsupported content type: \u001B[39m\u001B[38;5;132;01m{0}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(content_type)\n\u001B[1;32m 418\u001B[0m )\n\u001B[0;32m--> 420\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__deserialize\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdata\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mresponse_type\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:467\u001B[0m, in \u001B[0;36mApiClient.__deserialize\u001B[0;34m(self, data, klass)\u001B[0m\n\u001B[1;32m 465\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__deserialize_enum(data, klass)\n\u001B[1;32m 466\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m--> 467\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__deserialize_model\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdata\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mklass\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:788\u001B[0m, in \u001B[0;36mApiClient.__deserialize_model\u001B[0;34m(self, data, klass)\u001B[0m\n\u001B[1;32m 780\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21m__deserialize_model\u001B[39m(\u001B[38;5;28mself\u001B[39m, data, klass):\n\u001B[1;32m 781\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Deserializes list or dict to model.\u001B[39;00m\n\u001B[1;32m 782\u001B[0m \n\u001B[1;32m 783\u001B[0m \u001B[38;5;124;03m :param data: dict, list.\u001B[39;00m\n\u001B[1;32m 784\u001B[0m \u001B[38;5;124;03m :param klass: class literal.\u001B[39;00m\n\u001B[1;32m 785\u001B[0m \u001B[38;5;124;03m :return: model object.\u001B[39;00m\n\u001B[1;32m 786\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 788\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mklass\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mfrom_dict\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdata\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/models/sample_event_response.py:86\u001B[0m, in \u001B[0;36mSampleEventResponse.from_dict\u001B[0;34m(cls, obj)\u001B[0m\n\u001B[1;32m 83\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(obj, \u001B[38;5;28mdict\u001B[39m):\n\u001B[1;32m 84\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mcls\u001B[39m\u001B[38;5;241m.\u001B[39mmodel_validate(obj)\n\u001B[0;32m---> 86\u001B[0m _obj \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mcls\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel_validate\u001B[49m\u001B[43m(\u001B[49m\u001B[43m{\u001B[49m\n\u001B[1;32m 87\u001B[0m \u001B[43m \u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mid\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[43mobj\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mid\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 88\u001B[0m \u001B[43m \u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43msample_id\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[43mobj\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43msample_id\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 89\u001B[0m \u001B[43m \u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mevent_type\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[43mobj\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mevent_type\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 90\u001B[0m \u001B[43m \u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mtimestamp\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[43mobj\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mtimestamp\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[1;32m 91\u001B[0m \u001B[43m\u001B[49m\u001B[43m}\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 92\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m _obj\n",
|
||||
"File \u001B[0;32m/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pydantic/main.py:596\u001B[0m, in \u001B[0;36mBaseModel.model_validate\u001B[0;34m(cls, obj, strict, from_attributes, context)\u001B[0m\n\u001B[1;32m 594\u001B[0m \u001B[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001B[39;00m\n\u001B[1;32m 595\u001B[0m __tracebackhide__ \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[0;32m--> 596\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mcls\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__pydantic_validator__\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mvalidate_python\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 597\u001B[0m \u001B[43m \u001B[49m\u001B[43mobj\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mstrict\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mstrict\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mfrom_attributes\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mfrom_attributes\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcontext\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mcontext\u001B[49m\n\u001B[1;32m 598\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"\u001B[0;31mValidationError\u001B[0m: 3 validation errors for SampleEventResponse\nsample_id\n Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/int_type\nevent_type\n Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/string_type\ntimestamp\n Input should be a valid datetime [type=datetime_type, input_value=None, input_type=NoneType]\n For further information visit https://errors.pydantic.dev/2.9/v/datetime_type"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 110
|
||||
"execution_count": 4
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
@ -575,6 +557,126 @@
|
||||
}
|
||||
],
|
||||
"execution_count": 7
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-01-20T15:43:54.575154Z",
|
||||
"start_time": "2025-01-20T15:43:54.539295Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from aareDBclient import ApiClient, SamplesApi # Import the appropriate client\n",
|
||||
"from aareDBclient.rest import ApiException\n",
|
||||
"import mimetypes\n",
|
||||
"\n",
|
||||
"# File path to the image\n",
|
||||
"file_path = \"backend/tests/sample_image/IMG_1942.jpg\"\n",
|
||||
"\n",
|
||||
"# Sample ID\n",
|
||||
"sample_id = 433 # Replace with a valid sample_id from your FastAPI backend\n",
|
||||
"\n",
|
||||
"# Initialize the API client\n",
|
||||
"with ApiClient(configuration) as api_client:\n",
|
||||
" api_instance = SamplesApi(api_client) # Adjust as per your API structure\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" # Open the file and read as binary\n",
|
||||
" with open(file_path, \"rb\") as file:\n",
|
||||
" # Get the MIME type for the file\n",
|
||||
" mime_type, _ = mimetypes.guess_type(file_path)\n",
|
||||
"\n",
|
||||
" # Call the API method for uploading sample images\n",
|
||||
" response = api_instance.upload_sample_images_samples_samples_sample_id_upload_images_post(\n",
|
||||
" sample_id=sample_id,\n",
|
||||
" uploaded_files=[file.read()] # Pass raw bytes as a list\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Print the response from the API\n",
|
||||
" print(\"API Response:\")\n",
|
||||
" print(response)\n",
|
||||
"\n",
|
||||
" except ApiException as e:\n",
|
||||
" # Handle API exception gracefully\n",
|
||||
" print(\"Exception occurred while uploading the file:\")\n",
|
||||
" print(f\"Status Code: {e.status}\")\n",
|
||||
" if e.body:\n",
|
||||
" print(f\"Error Details: {e.body}\")"
|
||||
],
|
||||
"id": "40404614d1a63f95",
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "ValueError",
|
||||
"evalue": "Unsupported file value",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
|
||||
"\u001B[0;31mValueError\u001B[0m Traceback (most recent call last)",
|
||||
"Cell \u001B[0;32mIn[77], line 22\u001B[0m\n\u001B[1;32m 19\u001B[0m mime_type, _ \u001B[38;5;241m=\u001B[39m mimetypes\u001B[38;5;241m.\u001B[39mguess_type(file_path)\n\u001B[1;32m 21\u001B[0m \u001B[38;5;66;03m# Call the API method for uploading sample images\u001B[39;00m\n\u001B[0;32m---> 22\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[43mapi_instance\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mupload_sample_images_samples_samples_sample_id_upload_images_post\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 23\u001B[0m \u001B[43m \u001B[49m\u001B[43msample_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msample_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 24\u001B[0m \u001B[43m \u001B[49m\u001B[43muploaded_files\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m[\u001B[49m\u001B[43mfile\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mread\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m]\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;66;43;03m# Pass raw bytes as a list\u001B[39;49;00m\n\u001B[1;32m 25\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 27\u001B[0m \u001B[38;5;66;03m# Print the response from the API\u001B[39;00m\n\u001B[1;32m 28\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mAPI Response:\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n",
|
||||
"File \u001B[0;32m/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pydantic/validate_call_decorator.py:60\u001B[0m, in \u001B[0;36mvalidate_call.<locals>.validate.<locals>.wrapper_function\u001B[0;34m(*args, **kwargs)\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[38;5;129m@functools\u001B[39m\u001B[38;5;241m.\u001B[39mwraps(function)\n\u001B[1;32m 59\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mwrapper_function\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m---> 60\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mvalidate_call_wrapper\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py:96\u001B[0m, in \u001B[0;36mValidateCallWrapper.__call__\u001B[0;34m(self, *args, **kwargs)\u001B[0m\n\u001B[1;32m 95\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21m__call__\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39margs: Any, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs: Any) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Any:\n\u001B[0;32m---> 96\u001B[0m res \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__pydantic_validator__\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mvalidate_python\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpydantic_core\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mArgsKwargs\u001B[49m\u001B[43m(\u001B[49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 97\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__return_pydantic_validator__:\n\u001B[1;32m 98\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__return_pydantic_validator__(res)\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api/samples_api.py:875\u001B[0m, in \u001B[0;36mSamplesApi.upload_sample_images_samples_samples_sample_id_upload_images_post\u001B[0;34m(self, sample_id, uploaded_files, _request_timeout, _request_auth, _content_type, _headers, _host_index)\u001B[0m\n\u001B[1;32m 827\u001B[0m \u001B[38;5;129m@validate_call\u001B[39m\n\u001B[1;32m 828\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mupload_sample_images_samples_samples_sample_id_upload_images_post\u001B[39m(\n\u001B[1;32m 829\u001B[0m \u001B[38;5;28mself\u001B[39m,\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 843\u001B[0m _host_index: Annotated[StrictInt, Field(ge\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m0\u001B[39m, le\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m0\u001B[39m)] \u001B[38;5;241m=\u001B[39m \u001B[38;5;241m0\u001B[39m,\n\u001B[1;32m 844\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mobject\u001B[39m:\n\u001B[1;32m 845\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Upload Sample Images\u001B[39;00m\n\u001B[1;32m 846\u001B[0m \n\u001B[1;32m 847\u001B[0m \u001B[38;5;124;03m Uploads images for a sample and stores them in a directory structure: images/user/date/dewar_name/puck_name/position/. Args: sample_id (int): ID of the sample. uploaded_files (Union[List[UploadFile], List[bytes]]): List of image files (as UploadFile or raw bytes). db (Session): SQLAlchemy database session.\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 872\u001B[0m \u001B[38;5;124;03m :return: Returns the result object.\u001B[39;00m\n\u001B[1;32m 873\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m \u001B[38;5;66;03m# noqa: E501\u001B[39;00m\n\u001B[0;32m--> 875\u001B[0m _param \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_upload_sample_images_samples_samples_sample_id_upload_images_post_serialize\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 876\u001B[0m \u001B[43m \u001B[49m\u001B[43msample_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msample_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 877\u001B[0m \u001B[43m \u001B[49m\u001B[43muploaded_files\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43muploaded_files\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 878\u001B[0m \u001B[43m \u001B[49m\u001B[43m_request_auth\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_request_auth\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 879\u001B[0m \u001B[43m \u001B[49m\u001B[43m_content_type\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_content_type\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 880\u001B[0m \u001B[43m \u001B[49m\u001B[43m_headers\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_headers\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 881\u001B[0m \u001B[43m \u001B[49m\u001B[43m_host_index\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_host_index\u001B[49m\n\u001B[1;32m 882\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 884\u001B[0m _response_types_map: Dict[\u001B[38;5;28mstr\u001B[39m, Optional[\u001B[38;5;28mstr\u001B[39m]] \u001B[38;5;241m=\u001B[39m {\n\u001B[1;32m 885\u001B[0m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m200\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mobject\u001B[39m\u001B[38;5;124m\"\u001B[39m,\n\u001B[1;32m 886\u001B[0m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m422\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mHTTPValidationError\u001B[39m\u001B[38;5;124m\"\u001B[39m,\n\u001B[1;32m 887\u001B[0m }\n\u001B[1;32m 888\u001B[0m response_data \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mapi_client\u001B[38;5;241m.\u001B[39mcall_api(\n\u001B[1;32m 889\u001B[0m \u001B[38;5;241m*\u001B[39m_param,\n\u001B[1;32m 890\u001B[0m _request_timeout\u001B[38;5;241m=\u001B[39m_request_timeout\n\u001B[1;32m 891\u001B[0m )\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api/samples_api.py:1099\u001B[0m, in \u001B[0;36mSamplesApi._upload_sample_images_samples_samples_sample_id_upload_images_post_serialize\u001B[0;34m(self, sample_id, uploaded_files, _request_auth, _content_type, _headers, _host_index)\u001B[0m\n\u001B[1;32m 1095\u001B[0m \u001B[38;5;66;03m# authentication setting\u001B[39;00m\n\u001B[1;32m 1096\u001B[0m _auth_settings: List[\u001B[38;5;28mstr\u001B[39m] \u001B[38;5;241m=\u001B[39m [\n\u001B[1;32m 1097\u001B[0m ]\n\u001B[0;32m-> 1099\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mapi_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mparam_serialize\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 1100\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[38;5;124;43mPOST\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1101\u001B[0m \u001B[43m \u001B[49m\u001B[43mresource_path\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[38;5;124;43m/samples/samples/\u001B[39;49m\u001B[38;5;132;43;01m{sample_id}\u001B[39;49;00m\u001B[38;5;124;43m/upload-images\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1102\u001B[0m \u001B[43m \u001B[49m\u001B[43mpath_params\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_path_params\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1103\u001B[0m \u001B[43m \u001B[49m\u001B[43mquery_params\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_query_params\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1104\u001B[0m \u001B[43m \u001B[49m\u001B[43mheader_params\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_header_params\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1105\u001B[0m \u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_body_params\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1106\u001B[0m \u001B[43m \u001B[49m\u001B[43mpost_params\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_form_params\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1107\u001B[0m \u001B[43m \u001B[49m\u001B[43mfiles\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_files\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1108\u001B[0m \u001B[43m \u001B[49m\u001B[43mauth_settings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_auth_settings\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1109\u001B[0m \u001B[43m \u001B[49m\u001B[43mcollection_formats\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_collection_formats\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1110\u001B[0m \u001B[43m \u001B[49m\u001B[43m_host\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_host\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 1111\u001B[0m \u001B[43m \u001B[49m\u001B[43m_request_auth\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_request_auth\u001B[49m\n\u001B[1;32m 1112\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:214\u001B[0m, in \u001B[0;36mApiClient.param_serialize\u001B[0;34m(self, method, resource_path, path_params, query_params, header_params, body, post_params, files, auth_settings, collection_formats, _host, _request_auth)\u001B[0m\n\u001B[1;32m 209\u001B[0m post_params \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mparameters_to_tuples(\n\u001B[1;32m 210\u001B[0m post_params,\n\u001B[1;32m 211\u001B[0m collection_formats\n\u001B[1;32m 212\u001B[0m )\n\u001B[1;32m 213\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m files:\n\u001B[0;32m--> 214\u001B[0m post_params\u001B[38;5;241m.\u001B[39mextend(\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mfiles_parameters\u001B[49m\u001B[43m(\u001B[49m\u001B[43mfiles\u001B[49m\u001B[43m)\u001B[49m)\n\u001B[1;32m 216\u001B[0m \u001B[38;5;66;03m# auth setting\u001B[39;00m\n\u001B[1;32m 217\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mupdate_params_for_auth(\n\u001B[1;32m 218\u001B[0m header_params,\n\u001B[1;32m 219\u001B[0m query_params,\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 224\u001B[0m request_auth\u001B[38;5;241m=\u001B[39m_request_auth\n\u001B[1;32m 225\u001B[0m )\n",
|
||||
"File \u001B[0;32m~/PycharmProjects/heidi-v2/backend/aareDBclient/api_client.py:554\u001B[0m, in \u001B[0;36mApiClient.files_parameters\u001B[0;34m(self, files)\u001B[0m\n\u001B[1;32m 552\u001B[0m filedata \u001B[38;5;241m=\u001B[39m v\n\u001B[1;32m 553\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m--> 554\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUnsupported file value\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 555\u001B[0m mimetype \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 556\u001B[0m mimetypes\u001B[38;5;241m.\u001B[39mguess_type(filename)[\u001B[38;5;241m0\u001B[39m]\n\u001B[1;32m 557\u001B[0m \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mapplication/octet-stream\u001B[39m\u001B[38;5;124m'\u001B[39m\n\u001B[1;32m 558\u001B[0m )\n\u001B[1;32m 559\u001B[0m params\u001B[38;5;241m.\u001B[39mappend(\n\u001B[1;32m 560\u001B[0m \u001B[38;5;28mtuple\u001B[39m([k, \u001B[38;5;28mtuple\u001B[39m([filename, filedata, mimetype])])\n\u001B[1;32m 561\u001B[0m )\n",
|
||||
"\u001B[0;31mValueError\u001B[0m: Unsupported file value"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 77
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-01-20T15:14:51.219091Z",
|
||||
"start_time": "2025-01-20T15:14:51.216755Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": "help(api_instance.upload_sample_images_samples_samples_sample_id_upload_images_post)",
|
||||
"id": "8dd70634ffa5f37e",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Help on method upload_sample_images_samples_samples_sample_id_upload_images_post in module aareDBclient.api.samples_api:\n",
|
||||
"\n",
|
||||
"upload_sample_images_samples_samples_sample_id_upload_images_post(sample_id: Annotated[int, Strict(strict=True)], uploaded_files: List[Union[Annotated[bytes, Strict(strict=True)], Annotated[str, Strict(strict=True)]]], _request_timeout: Union[NoneType, Annotated[float, Strict(strict=True), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])], Tuple[Annotated[float, Strict(strict=True), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])], Annotated[float, Strict(strict=True), FieldInfo(annotation=NoneType, required=True, metadata=[Gt(gt=0)])]]] = None, _request_auth: Optional[Dict[Annotated[str, Strict(strict=True)], Any]] = None, _content_type: Optional[Annotated[str, Strict(strict=True)]] = None, _headers: Optional[Dict[Annotated[str, Strict(strict=True)], Any]] = None, _host_index: Annotated[int, Strict(strict=True), FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0), Le(le=0)])] = 0) -> object method of aareDBclient.api.samples_api.SamplesApi instance\n",
|
||||
" Upload Sample Images\n",
|
||||
"\n",
|
||||
" Uploads images for a sample and stores them in a directory structure: images/user/date/dewar_name/puck_name/position/. Args: sample_id (int): ID of the sample. uploaded_files (Union[List[UploadFile], List[bytes]]): List of image files (as UploadFile or raw bytes). db (Session): SQLAlchemy database session.\n",
|
||||
"\n",
|
||||
" :param sample_id: (required)\n",
|
||||
" :type sample_id: int\n",
|
||||
" :param uploaded_files: (required)\n",
|
||||
" :type uploaded_files: List[bytearray]\n",
|
||||
" :param _request_timeout: timeout setting for this request. If one\n",
|
||||
" number provided, it will be total request\n",
|
||||
" timeout. It can also be a pair (tuple) of\n",
|
||||
" (connection, read) timeouts.\n",
|
||||
" :type _request_timeout: int, tuple(int, int), optional\n",
|
||||
" :param _request_auth: set to override the auth_settings for an a single\n",
|
||||
" request; this effectively ignores the\n",
|
||||
" authentication in the spec for a single request.\n",
|
||||
" :type _request_auth: dict, optional\n",
|
||||
" :param _content_type: force content-type for the request.\n",
|
||||
" :type _content_type: str, Optional\n",
|
||||
" :param _headers: set to override the headers for a single\n",
|
||||
" request; this effectively ignores the headers\n",
|
||||
" in the spec for a single request.\n",
|
||||
" :type _headers: dict, optional\n",
|
||||
" :param _host_index: set to override the host_index for a single\n",
|
||||
" request; this effectively ignores the host_index\n",
|
||||
" in the spec for a single request.\n",
|
||||
" :type _host_index: int, optional\n",
|
||||
" :return: Returns the result object.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 51
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user