adithegod commited on
Commit
d29bde4
·
verified ·
1 Parent(s): 0fadcc1

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +4 -0
  2. .gitignore +6 -0
  3. Dockerfile +13 -0
  4. __pycache__/app.cpython-314.pyc +0 -0
  5. __pycache__/utils.cpython-314.pyc +0 -0
  6. app.py +261 -0
  7. generate_sample_csv.py +73 -0
  8. models/malware_label_encoder.pkl +3 -0
  9. models/malware_model.pkl +3 -0
  10. models/malware_scaler.pkl +3 -0
  11. models/model_comparison.csv +5 -0
  12. models/nids_label_encoder.pkl +3 -0
  13. models/nids_model.pkl +3 -0
  14. models/nids_scaler.pkl +3 -0
  15. requirements.txt +7 -0
  16. sample_nids.csv +11 -0
  17. utils.py +262 -0
  18. venv/.gitignore +2 -0
  19. venv/Lib/site-packages/__pycache__/six.cpython-314.pyc +0 -0
  20. venv/Lib/site-packages/__pycache__/threadpoolctl.cpython-314.pyc +0 -0
  21. venv/Lib/site-packages/__pycache__/typing_extensions.cpython-314.pyc +3 -0
  22. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/INSTALLER +1 -0
  23. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/METADATA +144 -0
  24. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/RECORD +11 -0
  25. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/WHEEL +4 -0
  26. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/entry_points.txt +4 -0
  27. venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/licenses/LICENSE +21 -0
  28. venv/Lib/site-packages/annotated_doc/__init__.py +3 -0
  29. venv/Lib/site-packages/annotated_doc/__pycache__/__init__.cpython-314.pyc +0 -0
  30. venv/Lib/site-packages/annotated_doc/__pycache__/main.cpython-314.pyc +0 -0
  31. venv/Lib/site-packages/annotated_doc/main.py +36 -0
  32. venv/Lib/site-packages/annotated_doc/py.typed +0 -0
  33. venv/Lib/site-packages/annotated_types-0.8.0.dist-info/INSTALLER +1 -0
  34. venv/Lib/site-packages/annotated_types-0.8.0.dist-info/METADATA +295 -0
  35. venv/Lib/site-packages/annotated_types-0.8.0.dist-info/RECORD +10 -0
  36. venv/Lib/site-packages/annotated_types-0.8.0.dist-info/WHEEL +4 -0
  37. venv/Lib/site-packages/annotated_types-0.8.0.dist-info/licenses/LICENSE +21 -0
  38. venv/Lib/site-packages/annotated_types/__init__.py +416 -0
  39. venv/Lib/site-packages/annotated_types/__pycache__/__init__.cpython-314.pyc +0 -0
  40. venv/Lib/site-packages/annotated_types/__pycache__/test_cases.cpython-314.pyc +0 -0
  41. venv/Lib/site-packages/annotated_types/py.typed +0 -0
  42. venv/Lib/site-packages/annotated_types/test_cases.py +146 -0
  43. venv/Lib/site-packages/anyio-4.14.2.dist-info/INSTALLER +1 -0
  44. venv/Lib/site-packages/anyio-4.14.2.dist-info/METADATA +107 -0
  45. venv/Lib/site-packages/anyio-4.14.2.dist-info/RECORD +96 -0
  46. venv/Lib/site-packages/anyio-4.14.2.dist-info/WHEEL +5 -0
  47. venv/Lib/site-packages/anyio-4.14.2.dist-info/entry_points.txt +2 -0
  48. venv/Lib/site-packages/anyio-4.14.2.dist-info/licenses/LICENSE +20 -0
  49. venv/Lib/site-packages/anyio-4.14.2.dist-info/scm_file_list.json +119 -0
  50. venv/Lib/site-packages/anyio-4.14.2.dist-info/scm_version.json +8 -0
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ venv/Lib/site-packages/__pycache__/typing_extensions.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
37
+ venv/Lib/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
38
+ venv/Lib/site-packages/anyio/_backends/__pycache__/_trio.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
39
+ venv/Lib/site-packages/click/__pycache__/core.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .ipynb_checkpoints/
5
+ uploads/*
6
+ test_backend.py
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /code
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
__pycache__/app.cpython-314.pyc ADDED
Binary file (12.6 kB). View file
 
__pycache__/utils.cpython-314.pyc ADDED
Binary file (13.5 kB). View file
 
app.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import shutil
4
+ import logging
5
+ import pandas as pd
6
+ import numpy as np
7
+ from contextlib import asynccontextmanager
8
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Request
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+
11
+ # Import our utility functions
12
+ from utils import (
13
+ load_pickle_file,
14
+ preprocess_nids_data,
15
+ preprocess_malware_data,
16
+ get_nids_threat_details,
17
+ get_malware_threat_details,
18
+ )
19
+
20
+ # Set up logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger("backend_app")
23
+
24
+ # Ensure uploads directory exists
25
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
26
+ UPLOADS_DIR = os.path.join(BASE_DIR, "uploads")
27
+ os.makedirs(UPLOADS_DIR, exist_ok=True)
28
+
29
+ # Risk levels mapping for prioritization
30
+ RISK_PRIORITY = {
31
+ "Critical": 4,
32
+ "High": 3,
33
+ "Medium": 2,
34
+ "Low": 1
35
+ }
36
+
37
+ # Lifespan context manager for loading models once at startup
38
+ @asynccontextmanager
39
+ async def lifespan(app: FastAPI):
40
+ logger.info("Starting FastAPI server: Loading ML models, scalers, and encoders...")
41
+ try:
42
+ # Load NIDS models
43
+ app.state.nids_model = load_pickle_file("nids_model.pkl")
44
+ app.state.nids_scaler = load_pickle_file("nids_scaler.pkl")
45
+ app.state.nids_label_encoder = load_pickle_file("nids_label_encoder.pkl")
46
+
47
+ # Load Malware models
48
+ app.state.malware_model = load_pickle_file("malware_model.pkl")
49
+ app.state.malware_scaler = load_pickle_file("malware_scaler.pkl")
50
+ app.state.malware_label_encoder = load_pickle_file("malware_label_encoder.pkl")
51
+
52
+ logger.info("All pre-trained ML models and scalers loaded successfully.")
53
+ except Exception as e:
54
+ logger.error(f"Critical error loading models during startup: {str(e)}")
55
+ # We do not crash the app startup, but subsequent requests will fail gracefully
56
+ # or we let it raise so the service fails to start if models are missing
57
+ raise RuntimeError(f"Server startup failed due to missing/invalid models: {str(e)}")
58
+
59
+ yield
60
+ logger.info("Shutting down FastAPI server: Clean up resources if necessary.")
61
+
62
+
63
+ app = FastAPI(
64
+ title="AI-Based Cyber Threat Detection Framework API",
65
+ description="Production-ready FastAPI backend for running NIDS and Malware predictions.",
66
+ version="1.0.0",
67
+ lifespan=lifespan
68
+ )
69
+
70
+ # Configure CORS Middleware
71
+ # Allows requests from any origin (e.g. frontend dev servers, Hugging Face Spaces, etc.)
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_origins=["*"],
75
+ allow_credentials=True,
76
+ allow_methods=["*"],
77
+ allow_headers=["*"],
78
+ )
79
+
80
+
81
+ @app.get("/")
82
+ async def root():
83
+ return {
84
+ "status": "online",
85
+ "message": "AI-Based Cyber Threat Detection Backend is running.",
86
+ "docs_url": "/docs"
87
+ }
88
+
89
+
90
+ @app.post("/predict/network")
91
+ async def predict_network(request: Request, file: UploadFile = File(...)):
92
+ """
93
+ Accepts a network traffic features CSV file, runs NIDS model prediction, and returns
94
+ predictions, confidence, risk levels, and security recommendations.
95
+ """
96
+ # 1. Validate file extension
97
+ if not file.filename.endswith(".csv"):
98
+ raise HTTPException(status_code=400, detail="Invalid file format. Only CSV files are supported.")
99
+
100
+ # 2. Save CSV to uploads folder for tracking/logging
101
+ file_path = os.path.join(UPLOADS_DIR, f"nids_{int(time.time())}_{file.filename}")
102
+ try:
103
+ with open(file_path, "wb") as buffer:
104
+ shutil.copyfileobj(file.file, buffer)
105
+ except Exception as e:
106
+ logger.error(f"Failed to save file: {str(e)}")
107
+ raise HTTPException(status_code=500, detail=f"Failed to process file upload: {str(e)}")
108
+
109
+ # 3. Read CSV into Pandas DataFrame
110
+ try:
111
+ df = pd.read_csv(file_path)
112
+ except Exception as e:
113
+ logger.error(f"Failed to read CSV: {str(e)}")
114
+ raise HTTPException(status_code=400, detail=f"Failed to parse CSV file: {str(e)}")
115
+
116
+ # 4. Check if DataFrame is empty
117
+ if df.empty:
118
+ raise HTTPException(status_code=400, detail="The uploaded CSV file contains no data.")
119
+
120
+ # 5. Preprocess and Run Inference
121
+ try:
122
+ # Retrieve models from app state
123
+ scaler = request.app.state.nids_scaler
124
+ model = request.app.state.nids_model
125
+ label_encoder = request.app.state.nids_label_encoder
126
+
127
+ # Run preprocessing (imputing, feature selection, scaling)
128
+ X_scaled = preprocess_nids_data(df, scaler)
129
+
130
+ # Run prediction
131
+ predictions = model.predict(X_scaled)
132
+
133
+ # Decode target labels
134
+ decoded_labels = label_encoder.inverse_transform(predictions)
135
+
136
+ # Get confidence/probabilities
137
+ if hasattr(model, "predict_proba"):
138
+ probs = model.predict_proba(X_scaled)
139
+ confidences = np.max(probs, axis=1).tolist()
140
+ else:
141
+ # Fallback if model does not support predict_proba
142
+ confidences = [1.0] * len(predictions)
143
+
144
+ # Generate list of individual predictions
145
+ results = []
146
+ for idx, (label, conf) in enumerate(zip(decoded_labels, confidences)):
147
+ details = get_nids_threat_details(label, conf)
148
+ results.append({
149
+ "row_index": idx,
150
+ "prediction": str(label),
151
+ "confidence": round(float(conf), 4),
152
+ "risk_level": details["risk_level"],
153
+ "recommendation": details["recommendation"]
154
+ })
155
+
156
+ # Determine overall threat summary (highest threat in the batch)
157
+ highest_threat_result = max(results, key=lambda x: RISK_PRIORITY.get(x["risk_level"], 0))
158
+
159
+ return {
160
+ "prediction": highest_threat_result["prediction"],
161
+ "confidence": highest_threat_result["confidence"],
162
+ "risk_level": highest_threat_result["risk_level"],
163
+ "recommendation": highest_threat_result["recommendation"],
164
+ "is_batch": len(results) > 1,
165
+ "total_samples": len(results),
166
+ "results": results
167
+ }
168
+
169
+ except ValueError as val_err:
170
+ logger.warning(f"Validation error in NIDS processing: {str(val_err)}")
171
+ raise HTTPException(status_code=400, detail=str(val_err))
172
+ except Exception as e:
173
+ logger.error(f"Prediction logic error: {str(e)}")
174
+ raise HTTPException(status_code=500, detail=f"An error occurred during prediction processing: {str(e)}")
175
+
176
+
177
+ @app.post("/predict/malware")
178
+ async def predict_malware(request: Request, file: UploadFile = File(...)):
179
+ """
180
+ Accepts a malware classification features CSV file, runs malware prediction model, and returns
181
+ predictions, confidence, risk levels, and security recommendations.
182
+ """
183
+ # 1. Validate file extension
184
+ if not file.filename.endswith(".csv"):
185
+ raise HTTPException(status_code=400, detail="Invalid file format. Only CSV files are supported.")
186
+
187
+ # 2. Save CSV to uploads folder for tracking/logging
188
+ file_path = os.path.join(UPLOADS_DIR, f"malware_{int(time.time())}_{file.filename}")
189
+ try:
190
+ with open(file_path, "wb") as buffer:
191
+ shutil.copyfileobj(file.file, buffer)
192
+ except Exception as e:
193
+ logger.error(f"Failed to save file: {str(e)}")
194
+ raise HTTPException(status_code=500, detail=f"Failed to process file upload: {str(e)}")
195
+
196
+ # 3. Read CSV into Pandas DataFrame
197
+ try:
198
+ df = pd.read_csv(file_path)
199
+ except Exception as e:
200
+ logger.error(f"Failed to read CSV: {str(e)}")
201
+ raise HTTPException(status_code=400, detail=f"Failed to parse CSV file: {str(e)}")
202
+
203
+ # 4. Check if DataFrame is empty
204
+ if df.empty:
205
+ raise HTTPException(status_code=400, detail="The uploaded CSV file contains no data.")
206
+
207
+ # 5. Preprocess and Run Inference
208
+ try:
209
+ # Retrieve models from app state
210
+ scaler = request.app.state.malware_scaler
211
+ model = request.app.state.malware_model
212
+ label_encoder = request.app.state.malware_label_encoder
213
+
214
+ # Run preprocessing (imputing, feature selection, scaling)
215
+ X_scaled = preprocess_malware_data(df, scaler)
216
+
217
+ # Run prediction
218
+ predictions = model.predict(X_scaled)
219
+
220
+ # Decode target labels
221
+ decoded_labels = label_encoder.inverse_transform(predictions)
222
+
223
+ # Get confidence/probabilities
224
+ if hasattr(model, "predict_proba"):
225
+ probs = model.predict_proba(X_scaled)
226
+ confidences = np.max(probs, axis=1).tolist()
227
+ else:
228
+ # Fallback if model does not support predict_proba
229
+ confidences = [1.0] * len(predictions)
230
+
231
+ # Generate list of individual predictions
232
+ results = []
233
+ for idx, (label, conf) in enumerate(zip(decoded_labels, confidences)):
234
+ details = get_malware_threat_details(label, conf)
235
+ results.append({
236
+ "row_index": idx,
237
+ "prediction": str(label),
238
+ "confidence": round(float(conf), 4),
239
+ "risk_level": details["risk_level"],
240
+ "recommendation": details["recommendation"]
241
+ })
242
+
243
+ # Determine overall threat summary (highest threat in the batch)
244
+ highest_threat_result = max(results, key=lambda x: RISK_PRIORITY.get(x["risk_level"], 0))
245
+
246
+ return {
247
+ "prediction": highest_threat_result["prediction"],
248
+ "confidence": highest_threat_result["confidence"],
249
+ "risk_level": highest_threat_result["risk_level"],
250
+ "recommendation": highest_threat_result["recommendation"],
251
+ "is_batch": len(results) > 1,
252
+ "total_samples": len(results),
253
+ "results": results
254
+ }
255
+
256
+ except ValueError as val_err:
257
+ logger.warning(f"Validation error in malware processing: {str(val_err)}")
258
+ raise HTTPException(status_code=400, detail=str(val_err))
259
+ except Exception as e:
260
+ logger.error(f"Prediction logic error: {str(e)}")
261
+ raise HTTPException(status_code=500, detail=f"An error occurred during prediction processing: {str(e)}")
generate_sample_csv.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import joblib
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ # Define paths
7
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
8
+ MODELS_DIR = os.path.join(BASE_DIR, "models")
9
+ SCALER_PATH = os.path.join(MODELS_DIR, "nids_scaler.pkl")
10
+ OUTPUT_PATH = os.path.join(BASE_DIR, "sample_nids.csv")
11
+
12
+ print("=== Generating Sample NIDS CSV ===")
13
+
14
+ # 1. Load the NIDS scaler
15
+ if not os.path.exists(SCALER_PATH):
16
+ raise FileNotFoundError(f"Scaler not found at {SCALER_PATH}. Please make sure files are in the models folder.")
17
+
18
+ try:
19
+ scaler = joblib.load(SCALER_PATH)
20
+ print("Successfully loaded nids_scaler.pkl")
21
+ except Exception as e:
22
+ raise RuntimeError(f"Error loading scaler: {str(e)}")
23
+
24
+ # 2. Get expected feature names
25
+ if not hasattr(scaler, "feature_names_in_"):
26
+ raise ValueError("The loaded scaler does not contain feature_names_in_. Cannot determine column names automatically.")
27
+
28
+ features = list(scaler.feature_names_in_)
29
+ print(f"Scaler expects {len(features)} features.")
30
+
31
+ # 3. Create realistic random values for NIDS dataset (based on KDD/NSL-KDD schema)
32
+ np.random.seed(42) # For reproducibility
33
+ num_rows = 10
34
+ data = {}
35
+
36
+ # Define typical categoricals and binary flags
37
+ categorical_choices = {
38
+ "protocol_type": ["tcp", "udp", "icmp"],
39
+ "service": ["http", "private", "smtp", "ftp_data", "domain_u", "other"],
40
+ "flag": ["SF", "S0", "REJ", "RSTR", "RSTO", "SH"]
41
+ }
42
+
43
+ binary_columns = ["land", "logged_in", "root_shell", "is_host_login", "is_guest_login"]
44
+
45
+ for col in features:
46
+ if col in categorical_choices:
47
+ data[col] = np.random.choice(categorical_choices[col], size=num_rows)
48
+ elif col in binary_columns:
49
+ data[col] = np.random.choice([0, 1], size=num_rows, p=[0.9, 0.1])
50
+ elif "rate" in col:
51
+ # Rates are values between 0.0 and 1.0
52
+ data[col] = np.round(np.random.uniform(0.0, 1.0, size=num_rows), 4)
53
+ elif "count" in col:
54
+ # Counts are integers, e.g., 0 to 511
55
+ data[col] = np.random.randint(0, 512, size=num_rows)
56
+ elif col in ["src_bytes", "dst_bytes"]:
57
+ # Bytes can be larger integers
58
+ data[col] = np.random.randint(0, 100000, size=num_rows)
59
+ elif col == "duration":
60
+ # Connection duration
61
+ data[col] = np.random.randint(0, 1000, size=num_rows)
62
+ else:
63
+ # Default fallback for other numeric columns
64
+ data[col] = np.round(np.random.uniform(0.0, 10.0, size=num_rows), 4)
65
+
66
+ # 4. Generate DataFrame and save to CSV
67
+ df = pd.DataFrame(data)
68
+ df.to_csv(OUTPUT_PATH, index=False)
69
+
70
+ print(f"\nSuccessfully generated sample CSV file: {OUTPUT_PATH}")
71
+ print(f"File contains {num_rows} rows and {len(df.columns)} columns.")
72
+ print("\nGenerated Column Names:")
73
+ print(list(df.columns))
models/malware_label_encoder.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7975ff23bb39595dea6c8269fde6c097def053484f61d47b4c498e54b579c9e0
3
+ size 493
models/malware_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d41d1b8b501d10b9adb395522279cecbd4114be18fc770684a9d79ffe9251718
3
+ size 107400
models/malware_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da35101329afd2aaccc78f6edd12831d1b85d20ee8f6afb717e0ad2ef32e71bc
3
+ size 1959
models/model_comparison.csv ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Model,Dataset,Accuracy,Weighted F1
2
+ MLP,NSL-KDD Network Intrusion,0.9906573239906573,0.9904813707063973
3
+ Random Forest,NSL-KDD Network Intrusion,0.996996996996997,0.9969694960841293
4
+ MLP,Malware Classification,0.975,0.975
5
+ Random Forest,Malware Classification,0.96125,0.9612461850640471
models/nids_label_encoder.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc8d1d24b7fefd85676a8ec7478e9eb2cc0dc15bc841e233a59cc9311c0ab7ea
3
+ size 590
models/nids_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ba786d0f1eea01e2997af0e00894969df41058f8087e6f97bdccf2e8668e545
3
+ size 132877
models/nids_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0a0f944df560d2d4a1b1f1ca0c5fc041d0e9e4a0aab62562649001fcccfa384
3
+ size 2527
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn>=0.28.0
3
+ pandas>=2.0.0
4
+ numpy>=1.24.0
5
+ scikit-learn>=1.2.0
6
+ python-multipart>=0.0.9
7
+ joblib>=1.3.0
sample_nids.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ duration,protocol_type,service,flag,src_bytes,dst_bytes,land,wrong_fragment,urgent,hot,num_failed_logins,logged_in,num_compromised,root_shell,su_attempted,num_root,num_file_creations,num_shells,num_access_files,num_outbound_cmds,is_host_login,is_guest_login,count,srv_count,serror_rate,srv_serror_rate,rerror_rate,srv_rerror_rate,same_srv_rate,diff_srv_rate,srv_diff_host_rate,dst_host_count,dst_host_srv_count,dst_host_same_srv_rate,dst_host_diff_srv_rate,dst_host_same_src_port_rate,dst_host_srv_diff_host_rate,dst_host_serror_rate,dst_host_srv_serror_rate,dst_host_rerror_rate,dst_host_srv_rerror_rate
2
+ 102,icmp,other,SF,67435,87313,0,7.7513,2.7135,1.9872,6.233,0,6.3641,0,8.9609,2.2211,2.5178,2.3956,6.3231,0.1659,0,0,74,509,0.6919,0.5683,0.5026,0.9539,0.3851,0.9901,0.8101,470,152,0.0373,0.5314,0.0785,0.7146,0.7578,0.4735,0.8565,0.6433
3
+ 435,icmp,private,SF,56886,10627,0,9.395,8.2874,0.0552,3.309,0,3.1436,1,3.18,1.1987,4.9725,1.4489,6.3353,5.1209,1,1,112,472,0.652,0.0937,0.5769,0.9149,0.8511,0.1401,0.8671,142,185,0.8226,0.5406,0.0254,0.6602,0.0144,0.1732,0.6587,0.4583
4
+ 860,icmp,ftp_data,REJ,66803,8792,1,8.9483,3.5675,8.1546,0.6356,0,5.0857,0,1.1005,3.3762,3.0088,4.8945,5.3577,2.265,0,0,455,98,0.2243,0.3677,0.4925,0.3702,0.3169,0.5183,0.9132,91,62,0.3602,0.6374,0.9626,0.2799,0.1161,0.4339,0.1629,0.5456
5
+ 270,icmp,domain_u,REJ,31551,73969,0,5.979,2.8093,7.0686,3.1098,0,9.0757,0,2.2794,9.4291,2.8484,9.8565,0.9029,6.4517,0,0,419,152,0.7122,0.2652,0.1952,0.0155,0.1695,0.8774,0.5113,353,189,0.1271,0.7261,0.836,0.9549,0.046,0.3985,0.0706,0.9415
6
+ 106,tcp,http,S0,11394,43001,0,9.2187,5.427,7.2901,3.2518,0,2.4929,0,4.2711,3.232,0.3689,2.4206,8.353,1.7437,0,0,421,348,0.2372,0.244,0.7225,0.9283,0.5568,0.7408,0.5015,321,124,0.5222,0.9759,0.696,0.7379,0.0407,0.6159,0.6424,0.3861
7
+ 71,icmp,ftp_data,RSTR,69092,76552,0,0.8849,1.4092,7.7127,7.2961,0,4.1038,0,8.1801,5.1879,6.0956,6.7214,3.2078,6.9094,0,0,103,401,0.3254,0.973,0.2808,0.4282,0.9362,0.697,0.7983,287,149,0.77,0.5163,0.409,0.5544,0.8555,0.6351,0.0265,0.9612
8
+ 700,udp,private,RSTR,3890,23897,0,1.9598,8.022,0.7404,6.3756,0,7.5555,0,8.6073,7.0302,5.0268,7.6162,1.8652,3.8674,0,0,339,383,0.7465,0.3931,0.0243,0.9667,0.696,0.7025,0.65,214,313,0.2158,0.323,0.1733,0.6117,0.7037,0.0453,0.5858,0.9054
9
+ 20,tcp,other,SH,41606,68148,0,0.4523,0.7455,3.5847,8.8721,0,2.288,0,0.0695,3.6363,0.5148,2.3764,0.4078,9.3673,0,0,253,365,0.6496,0.892,0.6455,0.9636,0.5701,0.3595,0.702,446,57,0.6229,0.7952,0.1564,0.4196,0.4742,0.3746,0.9402,0.1958
10
+ 614,udp,domain_u,SH,96276,23483,0,3.2533,9.8689,1.1587,4.7221,0,0.7698,0,5.1075,9.7178,2.7865,7.2822,5.9089,1.3752,0,0,226,337,0.8492,0.6311,0.1771,0.853,0.0972,0.2936,0.7958,341,341,0.0853,0.2708,0.2502,0.2477,0.0978,0.6259,0.5755,0.0694
11
+ 121,udp,ftp_data,SH,80038,48555,1,3.8868,7.7224,8.631,1.1959,0,2.8975,0,4.1741,9.6245,9.0827,3.6778,6.7756,3.4107,0,0,111,193,0.6576,0.7948,0.9405,0.2944,0.615,0.8094,0.89,50,304,0.0517,0.439,0.5492,0.356,0.4916,0.5031,0.3882,0.1008
utils.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import joblib
3
+ import pandas as pd
4
+ import numpy as np
5
+ import logging
6
+
7
+ # Set up logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger("backend_utils")
10
+
11
+ # Base paths
12
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13
+ MODELS_DIR = os.path.join(BASE_DIR, "models")
14
+
15
+ def load_pickle_file(filename: str):
16
+ """
17
+ Helper function to load a pickle file from the models directory.
18
+ """
19
+ filepath = os.path.join(MODELS_DIR, filename)
20
+ if not os.path.exists(filepath):
21
+ logger.error(f"Required model file not found: {filepath}")
22
+ raise FileNotFoundError(f"Model file not found: {filename} in models/ directory.")
23
+ try:
24
+ data = joblib.load(filepath)
25
+ logger.info(f"Successfully loaded {filename}")
26
+ return data
27
+ except Exception as e:
28
+ logger.error(f"Failed to load {filename}: {str(e)}")
29
+ raise RuntimeError(f"Error loading {filename}: {str(e)}")
30
+
31
+ # ==========================================
32
+ # CATEGORICAL MAPPINGS FOR NIDS
33
+ # ==========================================
34
+
35
+ # Standard mappings for NSL-KDD / KDD-99 categorical columns (alphabetical order matches scikit-learn's default LabelEncoder)
36
+ PROTOCOL_MAP = {"icmp": 0, "tcp": 1, "udp": 2}
37
+
38
+ FLAG_MAP = {
39
+ "OTH": 0, "REJ": 1, "RSTO": 2, "RSTOS0": 3, "RSTR": 4,
40
+ "S0": 5, "S1": 6, "S2": 7, "S3": 8, "SF": 9, "SH": 10
41
+ }
42
+
43
+ # Standard alphabetical list of services in NSL-KDD
44
+ SERVICES_LIST = [
45
+ "IRC", "X11", "Z39_50", "aol", "auth", "bgp", "courier", "csnet_ns", "ctf", "daytime",
46
+ "discard", "domain", "domain_u", "echo", "eco_i", "ecr_i", "efs", "exec", "finger", "ftp",
47
+ "ftp_data", "gopher", "harvest", "hostnames", "http", "http_2784", "http_443", "http_8001",
48
+ "imap4", "iso_tsap", "jmp", "lpr", "mail", "map", "netbios_dgm", "netbios_ns", "netbios_ssn",
49
+ "netstat", "nnsp", "nntp", "ntp_u", "other", "pm_dump", "pop_2", "pop_3", "printer",
50
+ "private", "red_i", "remote_job", "rje", "shell", "smtp", "sql_net", "ssh", "sunrpc",
51
+ "supdup", "systat", "telnet", "tftp_u", "tim_i", "time", "urh_i", "urp_i", "uucp",
52
+ "uucp_path", "vmnet", "whois"
53
+ ]
54
+ SERVICE_MAP = {service.lower(): idx for idx, service in enumerate(SERVICES_LIST)}
55
+
56
+ def map_protocol(val):
57
+ if pd.isna(val):
58
+ return 1 # Default to tcp
59
+ v_str = str(val).strip().lower()
60
+ return PROTOCOL_MAP.get(v_str, 1)
61
+
62
+ def map_flag(val):
63
+ if pd.isna(val):
64
+ return 9 # Default to SF
65
+ v_str = str(val).strip().upper()
66
+ return FLAG_MAP.get(v_str, 9)
67
+
68
+ def map_service(val):
69
+ if pd.isna(val):
70
+ return SERVICE_MAP.get("other", 41)
71
+ v_str = str(val).strip().lower()
72
+ return SERVICE_MAP.get(v_str, SERVICE_MAP.get("other", 41))
73
+
74
+ # ==========================================
75
+ # PREPROCESSING FUNCTIONS
76
+ # ==========================================
77
+
78
+ def preprocess_nids_data(df: pd.DataFrame, scaler):
79
+ """
80
+ Preprocess Network Intrusion Detection (NIDS) data before inference.
81
+
82
+ --------------------------------------------------------------------------
83
+ COPY-PASTE INSTRUCTION FROM YOUR GOOGLE COLAB NOTEBOOK:
84
+ If you had specific preprocessing steps in your Colab notebook (such as
85
+ dropping certain columns like ID or timestamps, replacing missing values,
86
+ or encoding categorical variables), you should paste them here.
87
+ --------------------------------------------------------------------------
88
+ """
89
+ # Create copy to avoid SettingWithCopyWarning
90
+ df = df.copy()
91
+
92
+ # 1. Decode/map categorical text features to numeric codes if they are strings
93
+ if "protocol_type" in df.columns:
94
+ if pd.api.types.is_string_dtype(df["protocol_type"]):
95
+ df["protocol_type"] = df["protocol_type"].apply(map_protocol)
96
+
97
+ if "service" in df.columns:
98
+ if pd.api.types.is_string_dtype(df["service"]):
99
+ df["service"] = df["service"].apply(map_service)
100
+
101
+ if "flag" in df.columns:
102
+ if pd.api.types.is_string_dtype(df["flag"]):
103
+ df["flag"] = df["flag"].apply(map_flag)
104
+
105
+ # 2. Standard handling of missing values
106
+ df = df.fillna(0)
107
+
108
+ # 2. Align columns with the Scaler's expected inputs
109
+ # This automatically matches the column ordering and column list expected by the scaler.
110
+ if hasattr(scaler, "feature_names_in_"):
111
+ expected_cols = list(scaler.feature_names_in_)
112
+ missing_cols = [col for col in expected_cols if col not in df.columns]
113
+ if missing_cols:
114
+ raise ValueError(f"Missing required columns in uploaded NIDS CSV: {missing_cols}")
115
+ df_prepared = df[expected_cols]
116
+ else:
117
+ # Fallback if the scaler has no feature names (e.g. fit on raw numpy array)
118
+ # Select numeric columns only
119
+ df_prepared = df.select_dtypes(include=[np.number])
120
+ if hasattr(scaler, "n_features_in_"):
121
+ expected_dim = scaler.n_features_in_
122
+ if df_prepared.shape[1] != expected_dim:
123
+ raise ValueError(
124
+ f"Number of numeric columns ({df_prepared.shape[1]}) "
125
+ f"does not match model expected features ({expected_dim}). "
126
+ "Please ensure columns are in the correct order and format."
127
+ )
128
+
129
+ # 3. Apply the saved scaler
130
+ X_scaled = scaler.transform(df_prepared)
131
+ return X_scaled
132
+
133
+
134
+ def preprocess_malware_data(df: pd.DataFrame, scaler):
135
+ """
136
+ Preprocess Malware detection data before inference.
137
+
138
+ --------------------------------------------------------------------------
139
+ COPY-PASTE INSTRUCTION FROM YOUR GOOGLE COLAB NOTEBOOK:
140
+ If you had specific preprocessing steps in your Colab notebook (such as
141
+ dropping columns like file hashes/names, target labels, filling nulls,
142
+ or scaling specific features), you should paste them here.
143
+ --------------------------------------------------------------------------
144
+ """
145
+ # 1. Standard handling of missing values
146
+ df = df.fillna(0)
147
+
148
+ # 2. Align columns with the Scaler's expected inputs
149
+ if hasattr(scaler, "feature_names_in_"):
150
+ expected_cols = list(scaler.feature_names_in_)
151
+ missing_cols = [col for col in expected_cols if col not in df.columns]
152
+ if missing_cols:
153
+ raise ValueError(f"Missing required columns in uploaded Malware CSV: {missing_cols}")
154
+ df_prepared = df[expected_cols]
155
+ else:
156
+ # Fallback to numeric columns
157
+ df_prepared = df.select_dtypes(include=[np.number])
158
+ if hasattr(scaler, "n_features_in_"):
159
+ expected_dim = scaler.n_features_in_
160
+ if df_prepared.shape[1] != expected_dim:
161
+ raise ValueError(
162
+ f"Number of numeric columns ({df_prepared.shape[1]}) "
163
+ f"does not match model expected features ({expected_dim}). "
164
+ "Please ensure columns are in the correct order and format."
165
+ )
166
+
167
+ # 3. Apply the saved scaler
168
+ X_scaled = scaler.transform(df_prepared)
169
+ return X_scaled
170
+
171
+ # ==========================================
172
+ # POST-PREDICTION MAPPER FUNCTIONS
173
+ # ==========================================
174
+
175
+ def get_nids_threat_details(prediction: str, confidence: float):
176
+ """
177
+ Maps a prediction string and confidence score to a Risk Level and Recommendation.
178
+ """
179
+ pred_lower = str(prediction).lower()
180
+
181
+ if "normal" in pred_lower or "benign" in pred_lower:
182
+ return {
183
+ "risk_level": "Low",
184
+ "recommendation": "No threat detected. Continue standard network monitoring and log collection."
185
+ }
186
+ elif "dos" in pred_lower or "ddos" in pred_lower:
187
+ return {
188
+ "risk_level": "High",
189
+ "recommendation": "Potential Denial of Service (DoS/DDoS) attack detected. Enable rate-limiting, activate mitigation rules on your web application firewall, and check resource utilization."
190
+ }
191
+ elif "probe" in pred_lower or "scan" in pred_lower or "portscan" in pred_lower:
192
+ return {
193
+ "risk_level": "Medium",
194
+ "recommendation": "Network scanning or reconnaissance detected. Block scanning IP address(es) and ensure unnecessary ports are closed/secured."
195
+ }
196
+ elif "brute" in pred_lower or "exploit" in pred_lower or "botnet" in pred_lower or "infiltration" in pred_lower:
197
+ return {
198
+ "risk_level": "Critical",
199
+ "recommendation": "Active intrusion attempt or compromised host detected. Immediately isolate the infected network node, reset passwords, and run network security scans."
200
+ }
201
+ else:
202
+ # General default mapping based on confidence
203
+ if confidence > 0.8:
204
+ return {
205
+ "risk_level": "High",
206
+ "recommendation": "Highly suspicious network activity detected. Restrict communication with the source IP and verify system health."
207
+ }
208
+ elif confidence > 0.5:
209
+ return {
210
+ "risk_level": "Medium",
211
+ "recommendation": "Anomalous network activity detected. Monitor connection logs closely."
212
+ }
213
+ else:
214
+ return {
215
+ "risk_level": "Low",
216
+ "recommendation": "Low risk anomaly. Monitor for further suspicious patterns."
217
+ }
218
+
219
+
220
+ def get_malware_threat_details(prediction: str, confidence: float):
221
+ """
222
+ Maps a prediction string and confidence score to a Risk Level and Recommendation.
223
+ """
224
+ pred_lower = str(prediction).lower()
225
+
226
+ if "benign" in pred_lower or "normal" in pred_lower:
227
+ return {
228
+ "risk_level": "Low",
229
+ "recommendation": "File appears safe. No malicious signatures or behavior patterns identified."
230
+ }
231
+ elif "ransomware" in pred_lower:
232
+ return {
233
+ "risk_level": "Critical",
234
+ "recommendation": "Ransomware signature detected! Immediately isolate the system from all local networks and internet, disable shared drives, and restore from cold backups."
235
+ }
236
+ elif "trojan" in pred_lower or "spyware" in pred_lower or "adware" in pred_lower or "backdoor" in pred_lower:
237
+ return {
238
+ "risk_level": "High",
239
+ "recommendation": "Malicious code detected. Place the file in quarantine, run an endpoint security scan, and review running processes."
240
+ }
241
+ elif "virus" in pred_lower or "worm" in pred_lower:
242
+ return {
243
+ "risk_level": "High",
244
+ "recommendation": "Self-replicating malware detected. Terminate associated processes, delete the source file, and check network propagation logs."
245
+ }
246
+ else:
247
+ # General default mapping based on confidence
248
+ if confidence > 0.8:
249
+ return {
250
+ "risk_level": "High",
251
+ "recommendation": "Unclassified threat detected with high confidence. Quarantine the file and analyze in a sandbox environment."
252
+ }
253
+ elif confidence > 0.5:
254
+ return {
255
+ "risk_level": "Medium",
256
+ "recommendation": "Suspicious file characteristics detected. Flag for security administrator verification."
257
+ }
258
+ else:
259
+ return {
260
+ "risk_level": "Low",
261
+ "recommendation": "Low-confidence file alert. Keep endpoint protection updated."
262
+ }
venv/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Created by venv; see https://docs.python.org/3/library/venv.html
2
+ *
venv/Lib/site-packages/__pycache__/six.cpython-314.pyc ADDED
Binary file (43 kB). View file
 
venv/Lib/site-packages/__pycache__/threadpoolctl.cpython-314.pyc ADDED
Binary file (59.5 kB). View file
 
venv/Lib/site-packages/__pycache__/typing_extensions.cpython-314.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e386441848b0e94cef78a4872eca4b168f308cc308129e11c86bda1192176b8
3
+ size 179417
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/METADATA ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: annotated-doc
3
+ Version: 0.0.5
4
+ Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
5
+ Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Intended Audience :: Information Technology
9
+ Classifier: Intended Audience :: System Administrators
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Topic :: Internet
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
19
+ Classifier: Development Status :: 4 - Beta
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Programming Language :: Python :: 3.14
28
+ Project-URL: Homepage, https://github.com/fastapi/annotated-doc
29
+ Project-URL: Documentation, https://github.com/fastapi/annotated-doc
30
+ Project-URL: Repository, https://github.com/fastapi/annotated-doc
31
+ Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
32
+ Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
33
+ Requires-Python: >=3.9
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Annotated Doc
37
+
38
+ Document parameters, class attributes, return types, and variables inline, with `Annotated`.
39
+
40
+ <a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
41
+ <img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
42
+ </a>
43
+ <a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
44
+ <img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
45
+ </a>
46
+ <a href="https://pypi.org/project/annotated-doc" target="_blank">
47
+ <img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
48
+ </a>
49
+ <a href="https://pypi.org/project/annotated-doc" target="_blank">
50
+ <img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
51
+ </a>
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install annotated-doc
57
+ ```
58
+
59
+ Or with `uv`:
60
+
61
+ ```Python
62
+ uv add annotated-doc
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
68
+
69
+ For example, to document a parameter `name` in a function `hi` you could do:
70
+
71
+ ```Python
72
+ from typing import Annotated
73
+
74
+ from annotated_doc import Doc
75
+
76
+ def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
77
+ print(f"Hi, {name}!")
78
+ ```
79
+
80
+ You can also use it to document class attributes:
81
+
82
+ ```Python
83
+ from typing import Annotated
84
+
85
+ from annotated_doc import Doc
86
+
87
+ class User:
88
+ name: Annotated[str, Doc("The user's name")]
89
+ age: Annotated[int, Doc("The user's age")]
90
+ ```
91
+
92
+ The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
93
+
94
+ ## Who Uses This
95
+
96
+ `annotated-doc` was made for:
97
+
98
+ * [FastAPI](https://fastapi.tiangolo.com/)
99
+ * [Typer](https://typer.tiangolo.com/)
100
+ * [SQLModel](https://sqlmodel.tiangolo.com/)
101
+ * [Asyncer](https://asyncer.tiangolo.com/)
102
+
103
+ `annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
104
+
105
+ ## Reasons not to use `annotated-doc`
106
+
107
+ You are already comfortable with one of the existing docstring formats, like:
108
+
109
+ * Sphinx
110
+ * numpydoc
111
+ * Google
112
+ * Keras
113
+
114
+ Your team is already comfortable using them.
115
+
116
+ You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
117
+
118
+ You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
119
+
120
+ ## Reasons to use `annotated-doc`
121
+
122
+ * No micro-syntax to learn for newcomers, it’s **just Python** syntax.
123
+ * **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
124
+ * **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
125
+ * **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
126
+ * **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
127
+ * **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
128
+ * **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
129
+ * **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
130
+ * A more formalized way to document other symbols, like type aliases, that could use Annotated.
131
+ * **Support** for apps using FastAPI, Typer and others.
132
+ * **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
133
+
134
+ ## History
135
+
136
+ I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
137
+
138
+ The conclusion was that this was better done as an external effort, in a third-party library.
139
+
140
+ So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
141
+
142
+ ## License
143
+
144
+ This project is licensed under the terms of the MIT license.
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/RECORD ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated_doc-0.0.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ annotated_doc-0.0.5.dist-info/METADATA,sha256=jCTXDF2RU9IhIzU6kGBC_XYD3y0VwhlikLD4qWJVjEY,6516
3
+ annotated_doc-0.0.5.dist-info/RECORD,,
4
+ annotated_doc-0.0.5.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
5
+ annotated_doc-0.0.5.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
6
+ annotated_doc-0.0.5.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
7
+ annotated_doc/__init__.py,sha256=UqEV5evgeySQ5yvJQwfwTqwvMGfIwbxRPP4gf-o2-LA,52
8
+ annotated_doc/__pycache__/__init__.cpython-314.pyc,,
9
+ annotated_doc/__pycache__/main.cpython-314.pyc,,
10
+ annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
11
+ annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.9)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/entry_points.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+
venv/Lib/site-packages/annotated_doc-0.0.5.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Sebastián Ramírez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
venv/Lib/site-packages/annotated_doc/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .main import Doc as Doc
2
+
3
+ __version__ = "0.0.5"
venv/Lib/site-packages/annotated_doc/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (254 Bytes). View file
 
venv/Lib/site-packages/annotated_doc/__pycache__/main.cpython-314.pyc ADDED
Binary file (2.56 kB). View file
 
venv/Lib/site-packages/annotated_doc/main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Doc:
2
+ """Define the documentation of a type annotation using `Annotated`, to be
3
+ used in class attributes, function and method parameters, return values,
4
+ and variables.
5
+
6
+ The value should be a positional-only string literal to allow static tools
7
+ like editors and documentation generators to use it.
8
+
9
+ This complements docstrings.
10
+
11
+ The string value passed is available in the attribute `documentation`.
12
+
13
+ Example:
14
+
15
+ ```Python
16
+ from typing import Annotated
17
+ from annotated_doc import Doc
18
+
19
+ def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
20
+ print(f"Hi, {name}!")
21
+ ```
22
+ """
23
+
24
+ def __init__(self, documentation: str, /) -> None:
25
+ self.documentation = documentation
26
+
27
+ def __repr__(self) -> str:
28
+ return f"Doc({self.documentation!r})"
29
+
30
+ def __hash__(self) -> int:
31
+ return hash(self.documentation)
32
+
33
+ def __eq__(self, other: object) -> bool:
34
+ if not isinstance(other, Doc):
35
+ return NotImplemented
36
+ return self.documentation == other.documentation
venv/Lib/site-packages/annotated_doc/py.typed ADDED
File without changes
venv/Lib/site-packages/annotated_types-0.8.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/Lib/site-packages/annotated_types-0.8.0.dist-info/METADATA ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: annotated-types
3
+ Version: 0.8.0
4
+ Summary: Reusable constraint types to use with typing.Annotated
5
+ Project-URL: Homepage, https://github.com/annotated-types/annotated-types
6
+ Project-URL: Source, https://github.com/annotated-types/annotated-types
7
+ Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
8
+ Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: MacOS X
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Operating System :: Unix
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+
30
+ # annotated-types
31
+
32
+ [![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
33
+ [![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types)
34
+ [![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types)
35
+ [![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
36
+
37
+ [PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
38
+ adding context-specific metadata to existing types, and specifies that
39
+ `Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
40
+ logic for `x`.
41
+
42
+ This package provides metadata objects which can be used to represent common
43
+ constraints such as upper and lower bounds on scalar values and collection sizes,
44
+ a `Predicate` marker for runtime checks, and
45
+ descriptions of how we intend these metadata to be interpreted. In some cases,
46
+ we also note alternative representations which do not require this package.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install annotated-types
52
+ ```
53
+
54
+ ## Examples
55
+
56
+ ```python
57
+ from typing import Annotated
58
+ from annotated_types import Gt, Len, Predicate
59
+
60
+ class MyClass:
61
+ age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
62
+ # Invalid: 17, 18, "19", 19.0, ...
63
+ factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
64
+ # Invalid: 4, 8, -2, 5.0, "prime", ...
65
+
66
+ my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
67
+ # Invalid: (1, 2), ["abc"], [0] * 20
68
+ ```
69
+
70
+ ## Documentation
71
+
72
+ _While `annotated-types` avoids runtime checks for performance, users should not
73
+ construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
74
+ Downstream implementors may choose to raise an error, emit a warning, silently ignore
75
+ a metadata item, etc., if the metadata objects described below are used with an
76
+ incompatible type - or for any other reason!_
77
+
78
+ ### Gt, Ge, Lt, Le
79
+
80
+ Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
81
+ dates, times, strings, sets, etc. Note that the boundary value need not be of the
82
+ same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
83
+ is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
84
+
85
+ We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
86
+ as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
87
+ the `annotated-types` package.
88
+
89
+ To be explicit, these types have the following meanings:
90
+
91
+ * `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
92
+ * `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
93
+ * `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
94
+ * `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
95
+
96
+ ### Interval
97
+
98
+ `Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
99
+ metadata object. `None` attributes should be ignored, and non-`None` attributes
100
+ treated as per the single bounds above.
101
+
102
+ ### MultipleOf
103
+
104
+ `MultipleOf(multiple_of=x)` might be interpreted in two ways:
105
+
106
+ 1. Python semantics, implying `value % multiple_of == 0`, or
107
+ 2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
108
+ where `int(value / multiple_of) == value / multiple_of`.
109
+
110
+ We encourage users to be aware of these two common interpretations and their
111
+ distinct behaviours, especially since very large or non-integer numbers make
112
+ it easy to cause silent data corruption due to floating-point imprecision.
113
+
114
+ We encourage libraries to carefully document which interpretation they implement.
115
+
116
+ ### MinLen, MaxLen, Len
117
+
118
+ `Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
119
+
120
+ As well as `Len()` which can optionally include upper and lower bounds, we also
121
+ provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
122
+ and `Len(max_length=y)` respectively.
123
+
124
+ `Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
125
+
126
+ Examples of usage:
127
+
128
+ * `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10)]`) - list must have a length of 10 or less
129
+ * `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
130
+ * `Annotated[list, MinLen(3)]` (or `Annotated[list, Len(min_length=3)]`) - list must have a length of 3 or more
131
+ * `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
132
+ * `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
133
+
134
+ #### Changed in v0.4.0
135
+
136
+ * `min_inclusive` has been renamed to `min_length`, no change in meaning
137
+ * `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
138
+ * The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
139
+ meaning of the upper bound in slices vs. `Len`
140
+
141
+ See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
142
+
143
+ ### Timezone
144
+
145
+ `Timezone` can be used with a `datetime` or a `time` to express which timezones
146
+ are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
147
+ `Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
148
+ expresses that any timezone-aware datetime is allowed. You may also pass a specific
149
+ timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
150
+ object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
151
+ allow a specific timezone, though we note that this is often a symptom of fragile design.
152
+
153
+ #### Changed in v0.x.x
154
+
155
+ * `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
156
+ `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
157
+
158
+ ### Unit
159
+
160
+ `Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
161
+ a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
162
+ would be a float representing a velocity in meters per second.
163
+
164
+ Please note that `annotated_types` itself makes no attempt to parse or validate
165
+ the unit string in any way. That is left entirely to downstream libraries,
166
+ such as [`pint`](https://pint.readthedocs.io) or
167
+ [`astropy.units`](https://docs.astropy.org/en/stable/units/).
168
+
169
+ An example of how a library might use this metadata:
170
+
171
+ ```python
172
+ from annotated_types import Unit
173
+ from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
174
+
175
+ # given a type annotated with a unit:
176
+ Meters = Annotated[float, Unit("m")]
177
+
178
+
179
+ # you can cast the annotation to a specific unit type with any
180
+ # callable that accepts a string and returns the desired type
181
+ T = TypeVar("T")
182
+ def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
183
+ if get_origin(tp) is Annotated:
184
+ for arg in get_args(tp):
185
+ if isinstance(arg, Unit):
186
+ return unit_cls(arg.unit)
187
+ return None
188
+
189
+
190
+ # using `pint`
191
+ import pint
192
+ pint_unit = cast_unit(Meters, pint.Unit)
193
+
194
+
195
+ # using `astropy.units`
196
+ import astropy.units as u
197
+ astropy_unit = cast_unit(Meters, u.Unit)
198
+ ```
199
+
200
+ ### Predicate
201
+
202
+ `Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
203
+ Users should prefer the statically inspectable metadata above, but if you need
204
+ the full power and flexibility of arbitrary runtime predicates... here it is.
205
+
206
+ For some common constraints, we provide generic types:
207
+
208
+ * `LowerCase = Annotated[T, Predicate(str.islower)]`
209
+ * `UpperCase = Annotated[T, Predicate(str.isupper)]`
210
+ * `IsDigit = Annotated[T, Predicate(str.isdigit)]`
211
+ * `IsFinite = Annotated[T, Predicate(math.isfinite)]`
212
+ * `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
213
+ * `IsNan = Annotated[T, Predicate(math.isnan)]`
214
+ * `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
215
+ * `IsInfinite = Annotated[T, Predicate(math.isinf)]`
216
+ * `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
217
+
218
+ so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
219
+ (but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
220
+
221
+ Some libraries might have special logic to handle known or understandable predicates,
222
+ for example by checking for `str.isdigit` and using its presence to both call custom
223
+ logic to enforce digit-only strings, and customise some generated external schema.
224
+ Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
225
+ favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
226
+
227
+ To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
228
+
229
+ We do not specify what behaviour should be expected for predicates that raise
230
+ an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
231
+ skip invalid constraints, or statically raise an error; or it might try calling it
232
+ and then propagate or discard the resulting
233
+ `TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
234
+ exception. We encourage libraries to document the behaviour they choose.
235
+
236
+ ### Doc
237
+
238
+ `doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
239
+
240
+ It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
241
+
242
+ It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
243
+
244
+ This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
245
+
246
+ ### Integrating downstream types with `GroupedMetadata`
247
+
248
+ Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
249
+ This can help reduce verbosity and cognitive overhead for users.
250
+ For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
251
+
252
+ ```python
253
+ from dataclasses import dataclass
254
+ from typing import Iterator
255
+ from annotated_types import GroupedMetadata, Ge
256
+
257
+ @dataclass
258
+ class Field(GroupedMetadata):
259
+ ge: int | None = None
260
+ description: str | None = None
261
+
262
+ def __iter__(self) -> Iterator[object]:
263
+ # Iterating over a GroupedMetadata object should yield annotated-types
264
+ # constraint metadata objects which describe it as fully as possible,
265
+ # and may include other unknown objects too.
266
+ if self.ge is not None:
267
+ yield Ge(self.ge)
268
+ if self.description is not None:
269
+ yield Description(self.description)
270
+ ```
271
+
272
+ Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
273
+
274
+ Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
275
+
276
+ Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
277
+
278
+ ### Consuming metadata
279
+
280
+ We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
281
+
282
+ It is up to the implementer to determine how this metadata is used.
283
+ You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
284
+
285
+ ## Design & History
286
+
287
+ This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
288
+ and Hypothesis, with the goal of making it as easy as possible for end-users to
289
+ provide more informative annotations for use by runtime libraries.
290
+
291
+ It is deliberately minimal, and following PEP-593 allows considerable downstream
292
+ discretion in what (if anything!) they choose to support. Nonetheless, we expect
293
+ that staying simple and covering _only_ the most common use-cases will give users
294
+ and maintainers the best experience we can. If you'd like more constraints for your
295
+ types - follow our lead, by defining them and documenting them downstream!
venv/Lib/site-packages/annotated_types-0.8.0.dist-info/RECORD ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated_types-0.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ annotated_types-0.8.0.dist-info/METADATA,sha256=YUmFsnj2Abjvhj-CeDuZHMOtpLifWS63HHxIxfLvIpg,15009
3
+ annotated_types-0.8.0.dist-info/RECORD,,
4
+ annotated_types-0.8.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
5
+ annotated_types-0.8.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
6
+ annotated_types/__init__.py,sha256=pxBKTUObJ6n3T8C-I2ubobeDHmBEAmgCogWrwSmKm8g,13273
7
+ annotated_types/__pycache__/__init__.cpython-314.pyc,,
8
+ annotated_types/__pycache__/test_cases.cpython-314.pyc,,
9
+ annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ annotated_types/test_cases.py,sha256=2GdHKstXuBzpf3iXKCjmvFZpyk2zP9Hm9kXGGDhGBo4,6310
venv/Lib/site-packages/annotated_types-0.8.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
venv/Lib/site-packages/annotated_types-0.8.0.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 the contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
venv/Lib/site-packages/annotated_types/__init__.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import types
3
+ from collections.abc import Callable, Iterator
4
+ from dataclasses import dataclass
5
+ from datetime import tzinfo
6
+ from types import EllipsisType
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Annotated,
10
+ Any,
11
+ Literal,
12
+ Protocol,
13
+ SupportsFloat,
14
+ SupportsIndex,
15
+ TypeVar,
16
+ Union,
17
+ runtime_checkable,
18
+ )
19
+
20
+ __all__ = (
21
+ 'BaseMetadata',
22
+ 'GroupedMetadata',
23
+ 'Gt',
24
+ 'Ge',
25
+ 'Lt',
26
+ 'Le',
27
+ 'Interval',
28
+ 'MultipleOf',
29
+ 'MinLen',
30
+ 'MaxLen',
31
+ 'Len',
32
+ 'Timezone',
33
+ 'Predicate',
34
+ 'LowerCase',
35
+ 'UpperCase',
36
+ 'IsDigits',
37
+ 'IsFinite',
38
+ 'IsNotFinite',
39
+ 'IsNan',
40
+ 'IsNotNan',
41
+ 'IsInfinite',
42
+ 'IsNotInfinite',
43
+ 'doc',
44
+ 'DocInfo',
45
+ '__version__',
46
+ )
47
+
48
+ __version__ = '0.8.0'
49
+
50
+
51
+ T = TypeVar('T')
52
+
53
+
54
+ # arguments that start with __ are considered
55
+ # positional only
56
+ # see https://peps.python.org/pep-0484/#positional-only-arguments
57
+
58
+
59
+ class SupportsGt(Protocol):
60
+ def __gt__(self: T, __other: T) -> bool:
61
+ ...
62
+
63
+
64
+ class SupportsGe(Protocol):
65
+ def __ge__(self: T, __other: T) -> bool:
66
+ ...
67
+
68
+
69
+ class SupportsLt(Protocol):
70
+ def __lt__(self: T, __other: T) -> bool:
71
+ ...
72
+
73
+
74
+ class SupportsLe(Protocol):
75
+ def __le__(self: T, __other: T) -> bool:
76
+ ...
77
+
78
+
79
+ class SupportsMod(Protocol):
80
+ def __mod__(self: T, __other: T) -> T:
81
+ ...
82
+
83
+
84
+ class SupportsDiv(Protocol):
85
+ def __div__(self: T, __other: T) -> T:
86
+ ...
87
+
88
+
89
+ class BaseMetadata:
90
+ """Base class for all metadata.
91
+
92
+ This exists mainly so that implementers
93
+ can do `isinstance(..., BaseMetadata)` while traversing field annotations.
94
+ """
95
+
96
+ __slots__ = ()
97
+
98
+
99
+ @dataclass(frozen=True, slots=True)
100
+ class Gt(BaseMetadata):
101
+ """Gt(gt=x) implies that the value must be greater than x.
102
+
103
+ It can be used with any type that supports the ``>`` operator,
104
+ including numbers, dates and times, strings, sets, and so on.
105
+ """
106
+
107
+ gt: SupportsGt
108
+
109
+
110
+ @dataclass(frozen=True, slots=True)
111
+ class Ge(BaseMetadata):
112
+ """Ge(ge=x) implies that the value must be greater than or equal to x.
113
+
114
+ It can be used with any type that supports the ``>=`` operator,
115
+ including numbers, dates and times, strings, sets, and so on.
116
+ """
117
+
118
+ ge: SupportsGe
119
+
120
+
121
+ @dataclass(frozen=True, slots=True)
122
+ class Lt(BaseMetadata):
123
+ """Lt(lt=x) implies that the value must be less than x.
124
+
125
+ It can be used with any type that supports the ``<`` operator,
126
+ including numbers, dates and times, strings, sets, and so on.
127
+ """
128
+
129
+ lt: SupportsLt
130
+
131
+
132
+ @dataclass(frozen=True, slots=True)
133
+ class Le(BaseMetadata):
134
+ """Le(le=x) implies that the value must be less than or equal to x.
135
+
136
+ It can be used with any type that supports the ``<=`` operator,
137
+ including numbers, dates and times, strings, sets, and so on.
138
+ """
139
+
140
+ le: SupportsLe
141
+
142
+
143
+ @runtime_checkable
144
+ class GroupedMetadata(Protocol):
145
+ """A grouping of multiple objects, like typing.Unpack.
146
+
147
+ `GroupedMetadata` on its own is not metadata and has no meaning.
148
+ All of the constraints and metadata should be fully expressable
149
+ in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
150
+
151
+ Concrete implementations should override `GroupedMetadata.__iter__()`
152
+ to add their own metadata.
153
+ For example:
154
+
155
+ >>> @dataclass
156
+ >>> class Field(GroupedMetadata):
157
+ >>> gt: float | None = None
158
+ >>> description: str | None = None
159
+ ...
160
+ >>> def __iter__(self) -> Iterable[object]:
161
+ >>> if self.gt is not None:
162
+ >>> yield Gt(self.gt)
163
+ >>> if self.description is not None:
164
+ >>> yield Description(self.gt)
165
+
166
+ Also see the implementation of `Interval` below for an example.
167
+
168
+ Parsers should recognize this and unpack it so that it can be used
169
+ both with and without unpacking:
170
+
171
+ - `Annotated[int, Field(...)]` (parser must unpack Field)
172
+ - `Annotated[int, *Field(...)]` (PEP-646)
173
+ """ # noqa: trailing-whitespace
174
+
175
+ @property
176
+ def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
177
+ return True
178
+
179
+ def __iter__(self) -> Iterator[object]:
180
+ ...
181
+
182
+ if not TYPE_CHECKING:
183
+ __slots__ = () # allow subclasses to use slots
184
+
185
+ def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
186
+ # Basic ABC like functionality without the complexity of an ABC
187
+ super().__init_subclass__(*args, **kwargs)
188
+ if cls.__iter__ is GroupedMetadata.__iter__:
189
+ raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
190
+
191
+ def __iter__(self) -> Iterator[object]: # noqa: F811
192
+ raise NotImplementedError # more helpful than "None has no attribute..." type errors
193
+
194
+
195
+ @dataclass(frozen=True, kw_only=True, slots=True)
196
+ class Interval(GroupedMetadata):
197
+ """Interval can express inclusive or exclusive bounds with a single object.
198
+
199
+ It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
200
+ are interpreted the same way as the single-bound constraints.
201
+ """
202
+
203
+ gt: SupportsGt | None = None
204
+ ge: SupportsGe | None = None
205
+ lt: SupportsLt | None = None
206
+ le: SupportsLe | None = None
207
+
208
+ def __iter__(self) -> Iterator[BaseMetadata]:
209
+ """Unpack an Interval into zero or more single-bounds."""
210
+ if self.gt is not None:
211
+ yield Gt(self.gt)
212
+ if self.ge is not None:
213
+ yield Ge(self.ge)
214
+ if self.lt is not None:
215
+ yield Lt(self.lt)
216
+ if self.le is not None:
217
+ yield Le(self.le)
218
+
219
+
220
+ @dataclass(frozen=True, slots=True)
221
+ class MultipleOf(BaseMetadata):
222
+ """MultipleOf(multiple_of=x) might be interpreted in two ways:
223
+
224
+ 1. Python semantics, implying ``value % multiple_of == 0``, or
225
+ 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
226
+
227
+ We encourage users to be aware of these two common interpretations,
228
+ and libraries to carefully document which they implement.
229
+ """
230
+
231
+ multiple_of: SupportsDiv | SupportsMod
232
+
233
+
234
+ @dataclass(frozen=True, slots=True)
235
+ class MinLen(BaseMetadata):
236
+ """
237
+ MinLen() implies minimum inclusive length,
238
+ e.g. ``len(value) >= min_length``.
239
+ """
240
+
241
+ min_length: Annotated[int, Ge(0)]
242
+
243
+
244
+ @dataclass(frozen=True, slots=True)
245
+ class MaxLen(BaseMetadata):
246
+ """
247
+ MaxLen() implies maximum inclusive length,
248
+ e.g. ``len(value) <= max_length``.
249
+ """
250
+
251
+ max_length: Annotated[int, Ge(0)]
252
+
253
+
254
+ @dataclass(frozen=True, slots=True)
255
+ class Len(GroupedMetadata):
256
+ """
257
+ Len() implies that ``min_length <= len(value) <= max_length``.
258
+
259
+ Upper bound may be omitted or ``None`` to indicate no upper length bound.
260
+ """
261
+
262
+ min_length: Annotated[int, Ge(0)] = 0
263
+ max_length: Annotated[int, Ge(0)] | None = None
264
+
265
+ def __iter__(self) -> Iterator[BaseMetadata]:
266
+ """Unpack a Len into zero or more single-bounds."""
267
+ if self.min_length > 0:
268
+ yield MinLen(self.min_length)
269
+ if self.max_length is not None:
270
+ yield MaxLen(self.max_length)
271
+
272
+
273
+ @dataclass(frozen=True, slots=True)
274
+ class Timezone(BaseMetadata):
275
+ """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
276
+
277
+ ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
278
+ ``Timezone(...)`` (the ellipsis literal) expresses that the datetime must be
279
+ tz-aware but any timezone is allowed.
280
+
281
+ You may also pass a specific timezone string or tzinfo object such as
282
+ ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
283
+ you only allow a specific timezone, though we note that this is often
284
+ a symptom of poor design.
285
+ """
286
+
287
+ tz: str | tzinfo | EllipsisType | None
288
+
289
+
290
+ @dataclass(frozen=True, slots=True)
291
+ class Unit(BaseMetadata):
292
+ """Indicates that the value is a physical quantity with the specified unit.
293
+
294
+ It is intended for usage with numeric types, where the value represents the
295
+ magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
296
+ or ``speed: Annotated[float, Unit('m/s')]``.
297
+
298
+ Interpretation of the unit string is left to the discretion of the consumer.
299
+ It is suggested to follow conventions established by python libraries that work
300
+ with physical quantities, such as
301
+
302
+ - ``pint`` : <https://pint.readthedocs.io/en/stable/>
303
+ - ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
304
+
305
+ For indicating a quantity with a certain dimensionality but without a specific unit
306
+ it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
307
+ Note, however, ``annotated_types`` itself makes no use of the unit string.
308
+ """
309
+
310
+ unit: str
311
+
312
+
313
+ @dataclass(frozen=True, slots=True)
314
+ class Predicate(BaseMetadata):
315
+ """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
316
+
317
+ Users should prefer statically inspectable metadata, but if you need the full
318
+ power and flexibility of arbitrary runtime predicates... here it is.
319
+
320
+ We provide a few predefined predicates for common string constraints:
321
+ ``LowerCase = Predicate(str.islower)``, ``UpperCase = Predicate(str.isupper)``, and
322
+ ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
323
+ can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
324
+
325
+ Some libraries might have special logic to handle certain predicates, e.g. by
326
+ checking for `str.isdigit` and using its presence to both call custom logic to
327
+ enforce digit-only strings, and customise some generated external schema.
328
+
329
+ We do not specify what behaviour should be expected for predicates that raise
330
+ an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
331
+ skip invalid constraints, or statically raise an error; or it might try calling it
332
+ and then propagate or discard the resulting exception.
333
+ """
334
+
335
+ func: Callable[[Any], bool]
336
+
337
+ def __repr__(self) -> str:
338
+ if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
339
+ return f"{self.__class__.__name__}({self.func!r})"
340
+ if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
341
+ namespace := getattr(self.func.__self__, "__name__", None)
342
+ ):
343
+ return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
344
+ if isinstance(self.func, type(str.isascii)): # method descriptor
345
+ return f"{self.__class__.__name__}({self.func.__qualname__})"
346
+ return f"{self.__class__.__name__}({self.func.__name__})"
347
+
348
+
349
+ @dataclass
350
+ class Not:
351
+ func: Callable[[Any], bool]
352
+
353
+ def __call__(self, __v: Any) -> bool:
354
+ return not self.func(__v)
355
+
356
+
357
+ _StrType = TypeVar("_StrType", bound=str)
358
+
359
+ LowerCase = Annotated[_StrType, Predicate(str.islower)]
360
+ """
361
+ Return True if the string is a lowercase string, False otherwise.
362
+
363
+ A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
364
+ """ # noqa: E501
365
+ UpperCase = Annotated[_StrType, Predicate(str.isupper)]
366
+ """
367
+ Return True if the string is an uppercase string, False otherwise.
368
+
369
+ A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
370
+ """ # noqa: E501
371
+ IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
372
+ IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
373
+ """
374
+ Return True if the string is a digit string, False otherwise.
375
+
376
+ A string is a digit string if all characters in the string are digits and there is at least one character in the string.
377
+ """ # noqa: E501
378
+ IsAscii = Annotated[_StrType, Predicate(str.isascii)]
379
+ """
380
+ Return True if all characters in the string are ASCII, False otherwise.
381
+
382
+ ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
383
+ """
384
+
385
+ _NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
386
+ IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
387
+ """Return True if x is neither an infinity nor a NaN, and False otherwise."""
388
+ IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
389
+ """Return True if x is one of infinity or NaN, and False otherwise"""
390
+ IsNan = Annotated[_NumericType, Predicate(math.isnan)]
391
+ """Return True if x is a NaN (not a number), and False otherwise."""
392
+ IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
393
+ """Return True if x is anything but NaN (not a number), and False otherwise."""
394
+ IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
395
+ """Return True if x is a positive or negative infinity, and False otherwise."""
396
+ IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
397
+ """Return True if x is neither a positive or negative infinity, and False otherwise."""
398
+
399
+ try:
400
+ # PEP 727 – Documentation in Annotated Metadata
401
+ from typing_extensions import Doc # type: ignore[attr-defined]
402
+ except ImportError:
403
+
404
+ @dataclass(frozen=True, slots=True)
405
+ class Doc: # type: ignore [no-redef]
406
+ """ "
407
+ The return value of doc(), mainly to be used by tools that want to extract the
408
+ Annotated documentation at runtime.
409
+ """
410
+
411
+ documentation: str
412
+ """The documentation string passed to doc()."""
413
+
414
+
415
+ DocInfo = Doc # backwards compatibility
416
+ doc = Doc
venv/Lib/site-packages/annotated_types/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (22.6 kB). View file
 
venv/Lib/site-packages/annotated_types/__pycache__/test_cases.cpython-314.pyc ADDED
Binary file (14.3 kB). View file
 
venv/Lib/site-packages/annotated_types/py.typed ADDED
File without changes
venv/Lib/site-packages/annotated_types/test_cases.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from collections.abc import Iterable, Iterator
3
+ from datetime import date, datetime, timedelta, timezone
4
+ from decimal import Decimal
5
+ from typing import Annotated, Any, NamedTuple
6
+
7
+ import annotated_types as at
8
+
9
+
10
+ class Case(NamedTuple):
11
+ """
12
+ A test case for `annotated_types`.
13
+ """
14
+
15
+ annotation: Any
16
+ valid_cases: Iterable[Any]
17
+ invalid_cases: Iterable[Any]
18
+
19
+
20
+ def cases() -> Iterable[Case]:
21
+ # Gt, Ge, Lt, Le
22
+ yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
23
+ yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
24
+ yield Case(
25
+ Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
26
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
27
+ [datetime(2000, 1, 1), datetime(1999, 12, 31)],
28
+ )
29
+ yield Case(
30
+ Annotated[datetime, at.Gt(date(2000, 1, 1))],
31
+ [date(2000, 1, 2), date(2000, 1, 3)],
32
+ [date(2000, 1, 1), date(1999, 12, 31)],
33
+ )
34
+ yield Case(
35
+ Annotated[datetime, at.Gt(Decimal('1.123'))],
36
+ [Decimal('1.1231'), Decimal('123')],
37
+ [Decimal('1.123'), Decimal('0')],
38
+ )
39
+
40
+ yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
41
+ yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
42
+ yield Case(
43
+ Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
44
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
45
+ [datetime(1998, 1, 1), datetime(1999, 12, 31)],
46
+ )
47
+
48
+ yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
49
+ yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
50
+ yield Case(
51
+ Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
52
+ [datetime(1999, 12, 31), datetime(1999, 12, 31)],
53
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
54
+ )
55
+
56
+ yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
57
+ yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
58
+ yield Case(
59
+ Annotated[datetime, at.Le(datetime(2000, 1, 1))],
60
+ [datetime(2000, 1, 1), datetime(1999, 12, 31)],
61
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
62
+ )
63
+
64
+ # Interval
65
+ yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
66
+ yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
67
+ yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
68
+ yield Case(
69
+ Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
70
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
71
+ [datetime(2000, 1, 1), datetime(2000, 1, 4)],
72
+ )
73
+
74
+ yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
75
+ yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
76
+
77
+ # lengths
78
+
79
+ yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
80
+ yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
81
+ yield Case(Annotated[list[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
82
+ yield Case(Annotated[list[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
83
+
84
+ yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
85
+ yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
86
+ yield Case(Annotated[list[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
87
+ yield Case(Annotated[list[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
88
+
89
+ yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
90
+ yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
91
+
92
+ yield Case(Annotated[dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
93
+ yield Case(Annotated[set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
94
+ yield Case(Annotated[tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
95
+
96
+ # Timezone
97
+
98
+ yield Case(
99
+ Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
100
+ )
101
+ yield Case(
102
+ Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
103
+ )
104
+ yield Case(
105
+ Annotated[datetime, at.Timezone(timezone.utc)],
106
+ [datetime(2000, 1, 1, tzinfo=timezone.utc)],
107
+ [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
108
+ )
109
+ yield Case(
110
+ Annotated[datetime, at.Timezone('Europe/London')],
111
+ [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
112
+ [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
113
+ )
114
+
115
+ # Quantity
116
+
117
+ yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
118
+
119
+ # predicate types
120
+
121
+ yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
122
+ yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
123
+ yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
124
+ yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
125
+
126
+ yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
127
+
128
+ yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
129
+ yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
130
+ yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
131
+ yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
132
+ yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
133
+ yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
134
+
135
+ # check stacked predicates
136
+ yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
137
+
138
+ # doc
139
+ yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
140
+
141
+ # custom GroupedMetadata
142
+ class MyCustomGroupedMetadata(at.GroupedMetadata):
143
+ def __iter__(self) -> Iterator[at.Predicate]:
144
+ yield at.Predicate(lambda x: float(x).is_integer())
145
+
146
+ yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
venv/Lib/site-packages/anyio-4.14.2.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/Lib/site-packages/anyio-4.14.2.dist-info/METADATA ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: anyio
3
+ Version: 4.14.2
4
+ Summary: High-level concurrency and networking framework on top of asyncio or Trio
5
+ Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
8
+ Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
9
+ Project-URL: Source code, https://github.com/agronholm/anyio
10
+ Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Framework :: AnyIO
14
+ Classifier: Typing :: Typed
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3.15
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE
26
+ Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
27
+ Requires-Dist: idna>=2.8
28
+ Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
29
+ Provides-Extra: trio
30
+ Requires-Dist: trio>=0.32.0; extra == "trio"
31
+ Dynamic: license-file
32
+
33
+ .. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
34
+ :target: https://github.com/agronholm/anyio/actions/workflows/test.yml
35
+ :alt: Build Status
36
+ .. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
37
+ :target: https://coveralls.io/github/agronholm/anyio?branch=master
38
+ :alt: Code Coverage
39
+ .. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
40
+ :target: https://anyio.readthedocs.io/en/latest/?badge=latest
41
+ :alt: Documentation
42
+ .. image:: https://badges.gitter.im/gitterHQ/gitter.svg
43
+ :target: https://gitter.im/python-trio/AnyIO
44
+ :alt: Gitter chat
45
+ .. image:: https://tidelift.com/badges/package/pypi/anyio
46
+ :target: https://tidelift.com/subscription/pkg/pypi-anyio
47
+ :alt: Tidelift
48
+
49
+ AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
50
+ Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
51
+ with the native SC of Trio itself.
52
+
53
+ Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
54
+ Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
55
+ refactoring necessary. It will blend in with the native libraries of your chosen backend.
56
+
57
+ To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
58
+ `here <https://anyio.readthedocs.io/en/stable/why.html>`_.
59
+
60
+ Documentation
61
+ -------------
62
+
63
+ View full documentation at: https://anyio.readthedocs.io/
64
+
65
+ Features
66
+ --------
67
+
68
+ AnyIO offers the following functionality:
69
+
70
+ * Task groups (nurseries_ in trio terminology)
71
+ * High-level networking (TCP, UDP and UNIX sockets)
72
+
73
+ * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
74
+ 3.8)
75
+ * async/await style UDP sockets (unlike asyncio where you still have to use Transports and
76
+ Protocols)
77
+
78
+ * A versatile API for byte streams and object streams
79
+ * Inter-task synchronization and communication (locks, conditions, events, semaphores, object
80
+ streams)
81
+ * Worker threads
82
+ * Subprocesses
83
+ * Subinterpreter support for code parallelization (on Python 3.13 and later)
84
+ * Asynchronous file I/O (using worker threads)
85
+ * Signal handling
86
+ * Asynchronous versions of the functools_ and itertools_ modules
87
+
88
+ AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
89
+ It even works with the popular Hypothesis_ library.
90
+
91
+ .. _asyncio: https://docs.python.org/3/library/asyncio.html
92
+ .. _Trio: https://github.com/python-trio/trio
93
+ .. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
94
+ .. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
95
+ .. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
96
+ .. _pytest: https://docs.pytest.org/en/latest/
97
+ .. _functools: https://docs.python.org/3/library/functools.html
98
+ .. _itertools: https://docs.python.org/3/library/itertools.html
99
+ .. _Hypothesis: https://hypothesis.works/
100
+
101
+ Security contact information
102
+ ----------------------------
103
+
104
+ To report a security vulnerability, please use the `Tidelift security contact`_.
105
+ Tidelift will coordinate the fix and disclosure.
106
+
107
+ .. _Tidelift security contact: https://tidelift.com/security
venv/Lib/site-packages/anyio-4.14.2.dist-info/RECORD ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio-4.14.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ anyio-4.14.2.dist-info/METADATA,sha256=xeb8Tf2DMxmROyh756MNmTXpIIx--RdjBiGFWqrE1Bk,4645
3
+ anyio-4.14.2.dist-info/RECORD,,
4
+ anyio-4.14.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
+ anyio-4.14.2.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
6
+ anyio-4.14.2.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
7
+ anyio-4.14.2.dist-info/scm_file_list.json,sha256=wDSXGv8Ehn5ZW5BhB-RlaAc16zY_OfO27qrlMfMMZy8,3654
8
+ anyio-4.14.2.dist-info/scm_version.json,sha256=KgaUx31SyaqGFQFKpG3FO9kCk-ygOKS7Uf0yk56OzaY,161
9
+ anyio-4.14.2.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
10
+ anyio/__init__.py,sha256=HitUIfzvAojSeaHVmJ9rFn8k_yI63G6s_jUL2QChf4U,6405
11
+ anyio/__pycache__/__init__.cpython-314.pyc,,
12
+ anyio/__pycache__/from_thread.cpython-314.pyc,,
13
+ anyio/__pycache__/functools.cpython-314.pyc,,
14
+ anyio/__pycache__/itertools.cpython-314.pyc,,
15
+ anyio/__pycache__/lowlevel.cpython-314.pyc,,
16
+ anyio/__pycache__/pytest_plugin.cpython-314.pyc,,
17
+ anyio/__pycache__/to_interpreter.cpython-314.pyc,,
18
+ anyio/__pycache__/to_process.cpython-314.pyc,,
19
+ anyio/__pycache__/to_thread.cpython-314.pyc,,
20
+ anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ anyio/_backends/__pycache__/__init__.cpython-314.pyc,,
22
+ anyio/_backends/__pycache__/_asyncio.cpython-314.pyc,,
23
+ anyio/_backends/__pycache__/_trio.cpython-314.pyc,,
24
+ anyio/_backends/_asyncio.py,sha256=eK0j8PI0O7y3bX8cUBsafoWP8wsXR-szP6ciyP4Mkkk,104400
25
+ anyio/_backends/_trio.py,sha256=hoTXL8zI81v4UE-YT_cr2fzh_zY1mKdmZD3oQHmeR0E,45583
26
+ anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ anyio/_core/__pycache__/__init__.cpython-314.pyc,,
28
+ anyio/_core/__pycache__/_asyncio_selector_thread.cpython-314.pyc,,
29
+ anyio/_core/__pycache__/_contextmanagers.cpython-314.pyc,,
30
+ anyio/_core/__pycache__/_eventloop.cpython-314.pyc,,
31
+ anyio/_core/__pycache__/_exceptions.cpython-314.pyc,,
32
+ anyio/_core/__pycache__/_fileio.cpython-314.pyc,,
33
+ anyio/_core/__pycache__/_resources.cpython-314.pyc,,
34
+ anyio/_core/__pycache__/_signals.cpython-314.pyc,,
35
+ anyio/_core/__pycache__/_sockets.cpython-314.pyc,,
36
+ anyio/_core/__pycache__/_streams.cpython-314.pyc,,
37
+ anyio/_core/__pycache__/_subprocesses.cpython-314.pyc,,
38
+ anyio/_core/__pycache__/_synchronization.cpython-314.pyc,,
39
+ anyio/_core/__pycache__/_tasks.cpython-314.pyc,,
40
+ anyio/_core/__pycache__/_tempfile.cpython-314.pyc,,
41
+ anyio/_core/__pycache__/_testing.cpython-314.pyc,,
42
+ anyio/_core/__pycache__/_typedattr.cpython-314.pyc,,
43
+ anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
44
+ anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
45
+ anyio/_core/_eventloop.py,sha256=ByZUeJD9alMfcyTseRo5IzTO0IltEul_Gyq9iqSjqDk,6658
46
+ anyio/_core/_exceptions.py,sha256=OfzLO4Z3Hog1TnipbIn72YNtkoYxS4lHW9MqKDeGc88,4936
47
+ anyio/_core/_fileio.py,sha256=hHfyV0bXDL-R2ZNnInwse3nmTAd36AIz1cBxgmAwzAQ,31358
48
+ anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
49
+ anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
50
+ anyio/_core/_sockets.py,sha256=HtjiSH-yzehlqh_LpD3PGIafDuUCwd8gueUuk5MFeNk,35288
51
+ anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
52
+ anyio/_core/_subprocesses.py,sha256=M2GCc4NKXCbB_GtEskJgndiM2b0VS0NK_ohmFxri7O8,7923
53
+ anyio/_core/_synchronization.py,sha256=kgPk88-eVOmY-pDNs-ReRbcEelY1a7YczuVBPsZwc8A,21591
54
+ anyio/_core/_tasks.py,sha256=y99vRi-AFzEv6kyvIwqrzuskE2NMiNItWnuWGk7eOr4,13126
55
+ anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624
56
+ anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
57
+ anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
58
+ anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
59
+ anyio/abc/__pycache__/__init__.cpython-314.pyc,,
60
+ anyio/abc/__pycache__/_eventloop.cpython-314.pyc,,
61
+ anyio/abc/__pycache__/_resources.cpython-314.pyc,,
62
+ anyio/abc/__pycache__/_sockets.cpython-314.pyc,,
63
+ anyio/abc/__pycache__/_streams.cpython-314.pyc,,
64
+ anyio/abc/__pycache__/_subprocesses.cpython-314.pyc,,
65
+ anyio/abc/__pycache__/_tasks.cpython-314.pyc,,
66
+ anyio/abc/__pycache__/_testing.cpython-314.pyc,,
67
+ anyio/abc/_eventloop.py,sha256=OqWYSEj0TmwL_xniCJt3_jHFWsuMk9THk8tCTGsKapI,10681
68
+ anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
69
+ anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124
70
+ anyio/abc/_streams.py,sha256=rnSwRy-6y80TPQtIXit5LjMMiU1CCWS1oMNsmJQHRTg,7582
71
+ anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
72
+ anyio/abc/_tasks.py,sha256=m-FtE4phxeNIELSG7A3H7VUz3jA2Ib5J2JIew8-PS6o,6642
73
+ anyio/abc/_testing.py,sha256=9YYM2AXsYFvf4PLjUEr6yRxDiUeB5QbY_gOg0X_C6lY,2034
74
+ anyio/from_thread.py,sha256=JYsbaCaIB_Iit6kNhtXSteJGt4PcQ7ncq0nIpcelIrg,19265
75
+ anyio/functools.py,sha256=T4JS8IXq-x1S0Lbo2owF8l9fza2KypO147QLeyz4cjs,11797
76
+ anyio/itertools.py,sha256=QV-9mnRCr2yBph8g01QFvN-bQ_Yle-8Sl13YSydBlMI,16168
77
+ anyio/lowlevel.py,sha256=-59484Z6K5jF7XJJhHxYHIKnvFH_uAeMhl9a_AfaSHI,6280
78
+ anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
+ anyio/pytest_plugin.py,sha256=paMpI_VMNQf2bir0LfvgMpXSiYJoHDzWdKUVTyoHmvQ,13609
80
+ anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
+ anyio/streams/__pycache__/__init__.cpython-314.pyc,,
82
+ anyio/streams/__pycache__/buffered.cpython-314.pyc,,
83
+ anyio/streams/__pycache__/file.cpython-314.pyc,,
84
+ anyio/streams/__pycache__/memory.cpython-314.pyc,,
85
+ anyio/streams/__pycache__/stapled.cpython-314.pyc,,
86
+ anyio/streams/__pycache__/text.cpython-314.pyc,,
87
+ anyio/streams/__pycache__/tls.cpython-314.pyc,,
88
+ anyio/streams/buffered.py,sha256=u7hCD8SNrYHcutG6K5wEiy1F88-pizgjvEFM22Kq2Cw,6746
89
+ anyio/streams/file.py,sha256=6jujI2m-QJITqqKFamrupX_DNsU7y2Fz3omLZxOLuY0,4524
90
+ anyio/streams/memory.py,sha256=ZmKWCLpyItOCXmvCQT-L8IyJHNFaB-OVJbrn88CaMo0,10776
91
+ anyio/streams/stapled.py,sha256=mDNF9Gj4deXfOuKSZmgkEG-QExYAKAGjYBHbrs-rJaQ,4486
92
+ anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
93
+ anyio/streams/tls.py,sha256=Gvs--YOoFxcyn-hakXOiPM8H-aEp8nsU6k9rTrKqJA4,15801
94
+ anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
95
+ anyio/to_process.py,sha256=jHw7v6XHBNIfYa6DqnqKY0gsRo8ScOCh8fo19fqFvjU,9848
96
+ anyio/to_thread.py,sha256=bYszW0lCDfTmLnXbAYic05HEe_Di_0UWKabgL2vU0T0,2750
venv/Lib/site-packages/anyio-4.14.2.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
venv/Lib/site-packages/anyio-4.14.2.dist-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [pytest11]
2
+ anyio = anyio.pytest_plugin
venv/Lib/site-packages/anyio-4.14.2.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Alex Grönholm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
venv/Lib/site-packages/anyio-4.14.2.dist-info/scm_file_list.json ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [
3
+ ".pre-commit-config.yaml",
4
+ "LICENSE",
5
+ "pyproject.toml",
6
+ "AGENTS.md",
7
+ "README.rst",
8
+ "CLAUDE.md",
9
+ ".readthedocs.yml",
10
+ ".gitignore",
11
+ "docs/tempfile.rst",
12
+ "docs/signals.rst",
13
+ "docs/synchronization.rst",
14
+ "docs/contextmanagers.rst",
15
+ "docs/testing.rst",
16
+ "docs/networking.rst",
17
+ "docs/contributing.rst",
18
+ "docs/index.rst",
19
+ "docs/versionhistory.rst",
20
+ "docs/threads.rst",
21
+ "docs/api.rst",
22
+ "docs/typedattrs.rst",
23
+ "docs/basics.rst",
24
+ "docs/fileio.rst",
25
+ "docs/cancellation.rst",
26
+ "docs/support.rst",
27
+ "docs/streams.rst",
28
+ "docs/why.rst",
29
+ "docs/tasks.rst",
30
+ "docs/migration.rst",
31
+ "docs/conf.py",
32
+ "docs/subprocesses.rst",
33
+ "docs/faq.rst",
34
+ "docs/subinterpreters.rst",
35
+ "src/anyio/functools.py",
36
+ "src/anyio/py.typed",
37
+ "src/anyio/__init__.py",
38
+ "src/anyio/pytest_plugin.py",
39
+ "src/anyio/itertools.py",
40
+ "src/anyio/to_interpreter.py",
41
+ "src/anyio/from_thread.py",
42
+ "src/anyio/to_process.py",
43
+ "src/anyio/to_thread.py",
44
+ "src/anyio/lowlevel.py",
45
+ "src/anyio/_backends/_trio.py",
46
+ "src/anyio/_backends/__init__.py",
47
+ "src/anyio/_backends/_asyncio.py",
48
+ "src/anyio/streams/memory.py",
49
+ "src/anyio/streams/__init__.py",
50
+ "src/anyio/streams/tls.py",
51
+ "src/anyio/streams/file.py",
52
+ "src/anyio/streams/text.py",
53
+ "src/anyio/streams/stapled.py",
54
+ "src/anyio/streams/buffered.py",
55
+ "src/anyio/abc/_eventloop.py",
56
+ "src/anyio/abc/__init__.py",
57
+ "src/anyio/abc/_sockets.py",
58
+ "src/anyio/abc/_tasks.py",
59
+ "src/anyio/abc/_subprocesses.py",
60
+ "src/anyio/abc/_resources.py",
61
+ "src/anyio/abc/_streams.py",
62
+ "src/anyio/abc/_testing.py",
63
+ "src/anyio/_core/_typedattr.py",
64
+ "src/anyio/_core/_eventloop.py",
65
+ "src/anyio/_core/__init__.py",
66
+ "src/anyio/_core/_tempfile.py",
67
+ "src/anyio/_core/_sockets.py",
68
+ "src/anyio/_core/_tasks.py",
69
+ "src/anyio/_core/_fileio.py",
70
+ "src/anyio/_core/_synchronization.py",
71
+ "src/anyio/_core/_subprocesses.py",
72
+ "src/anyio/_core/_resources.py",
73
+ "src/anyio/_core/_contextmanagers.py",
74
+ "src/anyio/_core/_exceptions.py",
75
+ "src/anyio/_core/_streams.py",
76
+ "src/anyio/_core/_signals.py",
77
+ "src/anyio/_core/_asyncio_selector_thread.py",
78
+ "src/anyio/_core/_testing.py",
79
+ "tests/test_itertools.py",
80
+ "tests/test_functools.py",
81
+ "tests/test_eventloop.py",
82
+ "tests/__init__.py",
83
+ "tests/test_to_thread.py",
84
+ "tests/test_from_thread.py",
85
+ "tests/test_lowlevel.py",
86
+ "tests/test_to_interpreter.py",
87
+ "tests/test_sockets.py",
88
+ "tests/test_typedattr.py",
89
+ "tests/test_to_process.py",
90
+ "tests/test_all_attributes.py",
91
+ "tests/test_synchronization.py",
92
+ "tests/test_debugging.py",
93
+ "tests/test_contextmanagers.py",
94
+ "tests/test_fileio.py",
95
+ "tests/conftest.py",
96
+ "tests/test_signals.py",
97
+ "tests/test_deprecations.py",
98
+ "tests/test_tempfile.py",
99
+ "tests/test_taskgroups.py",
100
+ "tests/test_pytest_plugin.py",
101
+ "tests/test_subprocesses.py",
102
+ "tests/streams/test_text.py",
103
+ "tests/streams/test_memory.py",
104
+ "tests/streams/__init__.py",
105
+ "tests/streams/test_file.py",
106
+ "tests/streams/test_stapled.py",
107
+ "tests/streams/test_tls.py",
108
+ "tests/streams/test_buffered.py",
109
+ ".github/pull_request_template.md",
110
+ ".github/dependabot.yml",
111
+ ".github/FUNDING.yml",
112
+ ".github/ISSUE_TEMPLATE/features_request.yaml",
113
+ ".github/ISSUE_TEMPLATE/bug_report.yaml",
114
+ ".github/ISSUE_TEMPLATE/config.yml",
115
+ ".github/workflows/test.yml",
116
+ ".github/workflows/test-downstream.yml",
117
+ ".github/workflows/publish.yml"
118
+ ]
119
+ }
venv/Lib/site-packages/anyio-4.14.2.dist-info/scm_version.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tag": "4.14.2",
3
+ "distance": 0,
4
+ "node": "gc384f99687c64c59ed8a11c3a0f11a2d57daff71",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-12"
8
+ }