fcp-python 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fcp_python/bridge.py +0 -3
- fcp_python/domain/mutation.py +3 -301
- fcp_python/domain/verbs.py +0 -2
- fcp_python/main.py +2 -7
- {fcp_python-0.1.6.dist-info → fcp_python-0.2.0.dist-info}/METADATA +2 -2
- {fcp_python-0.1.6.dist-info → fcp_python-0.2.0.dist-info}/RECORD +8 -8
- {fcp_python-0.1.6.dist-info → fcp_python-0.2.0.dist-info}/WHEEL +1 -1
- {fcp_python-0.1.6.dist-info → fcp_python-0.2.0.dist-info}/entry_points.txt +0 -0
fcp_python/bridge.py
CHANGED
|
@@ -47,8 +47,6 @@ slipstream fcp python_query "unused @file:src/utils.py"
|
|
|
47
47
|
#### Refactoring
|
|
48
48
|
```
|
|
49
49
|
slipstream fcp python "rename Config Settings"
|
|
50
|
-
slipstream fcp python "extract validate @file:server.py @lines:15-30"
|
|
51
|
-
slipstream fcp python "import os @file:main.py @line:5"
|
|
52
50
|
```
|
|
53
51
|
|
|
54
52
|
#### Selectors
|
|
@@ -57,7 +55,6 @@ slipstream fcp python "import os @file:main.py @line:5"
|
|
|
57
55
|
- `@module:NAME` — filter by module
|
|
58
56
|
- `@kind:KIND` — function, class, method, variable, constant, module, property
|
|
59
57
|
- `@line:N` — filter by line number
|
|
60
|
-
- `@lines:N-M` — line range (for extract)
|
|
61
58
|
- `@decorator:NAME` — filter by decorator (e.g. `@decorator:staticmethod`)
|
|
62
59
|
"""
|
|
63
60
|
|
fcp_python/domain/mutation.py
CHANGED
|
@@ -2,24 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from urllib.parse import unquote, urlparse
|
|
7
|
-
|
|
8
5
|
from fcp_core import VerbRegistry, parse_op, suggest, ParseError
|
|
9
6
|
|
|
10
|
-
from fcp_python.lsp.types import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
)
|
|
14
|
-
from fcp_python.lsp.workspace_edit import apply_workspace_edit, ApplyResult
|
|
15
|
-
from fcp_python.resolver.selectors import (
|
|
16
|
-
SelectorType,
|
|
17
|
-
parse_line_range,
|
|
18
|
-
parse_selector,
|
|
19
|
-
)
|
|
7
|
+
from fcp_python.lsp.types import WorkspaceEdit
|
|
8
|
+
from fcp_python.lsp.workspace_edit import apply_workspace_edit
|
|
9
|
+
from fcp_python.resolver.selectors import parse_selector
|
|
20
10
|
|
|
21
11
|
from .format import (
|
|
22
|
-
format_code_action_choices,
|
|
23
12
|
format_disambiguation,
|
|
24
13
|
format_error,
|
|
25
14
|
format_mutation_result,
|
|
@@ -49,42 +38,10 @@ async def dispatch_mutation(
|
|
|
49
38
|
match op.verb:
|
|
50
39
|
case "rename":
|
|
51
40
|
return await handle_rename(model, op.positionals, op.selectors)
|
|
52
|
-
case "extract":
|
|
53
|
-
return await handle_extract(model, op.positionals, op.selectors)
|
|
54
|
-
case "import":
|
|
55
|
-
return await handle_import(model, op.positionals, op.selectors)
|
|
56
41
|
case _:
|
|
57
42
|
return format_error(f"verb '{op.verb}' is not a mutation.", None)
|
|
58
43
|
|
|
59
44
|
|
|
60
|
-
async def ensure_file_synced(model: PythonModel, uri: str) -> None:
|
|
61
|
-
"""Open a file in LSP if not already open, to sync with disk."""
|
|
62
|
-
client = model.lsp_client
|
|
63
|
-
if client is None:
|
|
64
|
-
return
|
|
65
|
-
parsed = urlparse(uri)
|
|
66
|
-
if parsed.scheme != "file":
|
|
67
|
-
return
|
|
68
|
-
path = Path(unquote(parsed.path))
|
|
69
|
-
text = path.read_text()
|
|
70
|
-
await client.did_open(uri, text)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
async def sync_after_edit(model: PythonModel, result: ApplyResult) -> None:
|
|
74
|
-
"""After applying a WorkspaceEdit, sync all changed files with LSP."""
|
|
75
|
-
for uri, _ in result.files_changed:
|
|
76
|
-
await ensure_file_synced(model, uri)
|
|
77
|
-
for uri in result.files_created:
|
|
78
|
-
await ensure_file_synced(model, uri)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def file_uri(model: PythonModel, file_value: str) -> str:
|
|
82
|
-
"""Build a file URI from a selector value, using model root as base."""
|
|
83
|
-
if file_value.startswith("file://"):
|
|
84
|
-
return file_value
|
|
85
|
-
return f"{model.root_uri.rstrip('/')}/{file_value}"
|
|
86
|
-
|
|
87
|
-
|
|
88
45
|
# -- rename ---------------------------------------------------------------
|
|
89
46
|
|
|
90
47
|
async def handle_rename(
|
|
@@ -136,258 +93,3 @@ async def handle_rename(
|
|
|
136
93
|
"rename", f"{old_name} → {new_name}", result, model.root_uri
|
|
137
94
|
)
|
|
138
95
|
|
|
139
|
-
|
|
140
|
-
# -- extract ---------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
async def handle_extract(
|
|
143
|
-
model: PythonModel,
|
|
144
|
-
positionals: list[str],
|
|
145
|
-
selectors: list[str],
|
|
146
|
-
) -> str:
|
|
147
|
-
if not positionals:
|
|
148
|
-
return format_error("extract requires FUNC_NAME.", None)
|
|
149
|
-
func_name = positionals[0]
|
|
150
|
-
|
|
151
|
-
parsed_selectors = [s for s in (parse_selector(sel) for sel in selectors) if s is not None]
|
|
152
|
-
|
|
153
|
-
file_sel = next((s for s in parsed_selectors if s.selector_type == SelectorType.FILE), None)
|
|
154
|
-
lines_sel = next((s for s in parsed_selectors if s.selector_type == SelectorType.LINES), None)
|
|
155
|
-
|
|
156
|
-
if file_sel is None:
|
|
157
|
-
return format_error("extract requires @file:PATH selector.", None)
|
|
158
|
-
if lines_sel is None:
|
|
159
|
-
return format_error("extract requires @lines:N-M selector.", None)
|
|
160
|
-
|
|
161
|
-
line_range = parse_line_range(lines_sel.value)
|
|
162
|
-
if line_range is None:
|
|
163
|
-
return format_error(
|
|
164
|
-
f"invalid line range '{lines_sel.value}'. Use @lines:N-M.", None
|
|
165
|
-
)
|
|
166
|
-
start_line, end_line = line_range
|
|
167
|
-
|
|
168
|
-
uri = file_uri(model, file_sel.value)
|
|
169
|
-
# Convert 1-indexed user lines to 0-indexed LSP
|
|
170
|
-
lsp_start = start_line - 1 if start_line > 0 else start_line
|
|
171
|
-
lsp_end = end_line - 1 if end_line > 0 else end_line
|
|
172
|
-
|
|
173
|
-
client = model.lsp_client
|
|
174
|
-
assert client is not None
|
|
175
|
-
|
|
176
|
-
try:
|
|
177
|
-
await ensure_file_synced(model, uri)
|
|
178
|
-
except Exception as e:
|
|
179
|
-
return format_error(f"extract: {e}", None)
|
|
180
|
-
|
|
181
|
-
params = {
|
|
182
|
-
"textDocument": {"uri": uri},
|
|
183
|
-
"range": {
|
|
184
|
-
"start": {"line": lsp_start, "character": 0},
|
|
185
|
-
"end": {"line": lsp_end, "character": 999},
|
|
186
|
-
},
|
|
187
|
-
"context": {
|
|
188
|
-
"diagnostics": [],
|
|
189
|
-
"only": ["refactor.extract.function", "refactor.extract"],
|
|
190
|
-
"triggerKind": 1,
|
|
191
|
-
},
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
try:
|
|
195
|
-
raw_actions = await client.request("textDocument/codeAction", params)
|
|
196
|
-
except Exception as e:
|
|
197
|
-
return format_error(f"extract failed: {e}", None)
|
|
198
|
-
|
|
199
|
-
if not raw_actions:
|
|
200
|
-
raw_actions = []
|
|
201
|
-
|
|
202
|
-
actions = [CodeAction.from_dict(a) for a in raw_actions]
|
|
203
|
-
extract_actions = [
|
|
204
|
-
a for a in actions
|
|
205
|
-
if a.kind and a.kind.startswith("refactor.extract")
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
if not extract_actions:
|
|
209
|
-
return format_error("no extract action available for the selected range.", None)
|
|
210
|
-
|
|
211
|
-
if len(extract_actions) == 1:
|
|
212
|
-
action = extract_actions[0]
|
|
213
|
-
else:
|
|
214
|
-
# Prefer "Extract into function" over others
|
|
215
|
-
func_action = next(
|
|
216
|
-
(a for a in extract_actions if "function" in a.title.lower()), None
|
|
217
|
-
)
|
|
218
|
-
if func_action:
|
|
219
|
-
action = func_action
|
|
220
|
-
else:
|
|
221
|
-
preferred = next((a for a in extract_actions if a.is_preferred), None)
|
|
222
|
-
if preferred:
|
|
223
|
-
action = preferred
|
|
224
|
-
else:
|
|
225
|
-
return format_code_action_choices(extract_actions)
|
|
226
|
-
|
|
227
|
-
if action.edit is None:
|
|
228
|
-
return format_error("extract action has no edit.", None)
|
|
229
|
-
|
|
230
|
-
try:
|
|
231
|
-
apply_result = apply_workspace_edit(action.edit)
|
|
232
|
-
except Exception as e:
|
|
233
|
-
return format_error(f"failed to apply extract: {e}", None)
|
|
234
|
-
|
|
235
|
-
# Sync changed files
|
|
236
|
-
try:
|
|
237
|
-
await sync_after_edit(model, apply_result)
|
|
238
|
-
except Exception:
|
|
239
|
-
pass
|
|
240
|
-
|
|
241
|
-
# Follow-up rename: pylsp/rope generates a placeholder name.
|
|
242
|
-
rename_result = await _follow_up_rename(model, uri, func_name)
|
|
243
|
-
|
|
244
|
-
if rename_result is not None:
|
|
245
|
-
return format_mutation_result("extract", func_name, rename_result, model.root_uri)
|
|
246
|
-
return format_mutation_result("extract", func_name, apply_result, model.root_uri)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
async def _follow_up_rename(
|
|
250
|
-
model: PythonModel,
|
|
251
|
-
uri: str,
|
|
252
|
-
desired_name: str,
|
|
253
|
-
) -> ApplyResult | None:
|
|
254
|
-
"""After extract, rename the generated function to the user's desired name."""
|
|
255
|
-
client = model.lsp_client
|
|
256
|
-
if client is None:
|
|
257
|
-
return None
|
|
258
|
-
|
|
259
|
-
parsed = urlparse(uri)
|
|
260
|
-
if parsed.scheme != "file":
|
|
261
|
-
return None
|
|
262
|
-
path = Path(unquote(parsed.path))
|
|
263
|
-
try:
|
|
264
|
-
content = path.read_text()
|
|
265
|
-
except Exception:
|
|
266
|
-
return None
|
|
267
|
-
|
|
268
|
-
# Rope generates "extracted_function" or similar placeholder names
|
|
269
|
-
for generated_name in ["extracted_function", "extracted_method", "extracted_variable"]:
|
|
270
|
-
fn_pattern = f"def {generated_name}"
|
|
271
|
-
byte_offset = content.find(fn_pattern)
|
|
272
|
-
if byte_offset >= 0:
|
|
273
|
-
name_offset = byte_offset + 4 # "def ".len()
|
|
274
|
-
line = content[:name_offset].count("\n")
|
|
275
|
-
last_newline = content.rfind("\n", 0, name_offset)
|
|
276
|
-
col = name_offset - (last_newline + 1) if last_newline >= 0 else name_offset
|
|
277
|
-
|
|
278
|
-
params = {
|
|
279
|
-
"textDocument": {"uri": uri},
|
|
280
|
-
"position": {"line": line, "character": col},
|
|
281
|
-
"newName": desired_name,
|
|
282
|
-
}
|
|
283
|
-
try:
|
|
284
|
-
raw_edit = await client.request("textDocument/rename", params)
|
|
285
|
-
if raw_edit:
|
|
286
|
-
workspace_edit = WorkspaceEdit.from_dict(raw_edit)
|
|
287
|
-
return apply_workspace_edit(workspace_edit)
|
|
288
|
-
except Exception:
|
|
289
|
-
pass
|
|
290
|
-
break
|
|
291
|
-
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# -- import ---------------------------------------------------------------
|
|
296
|
-
|
|
297
|
-
async def handle_import(
|
|
298
|
-
model: PythonModel,
|
|
299
|
-
positionals: list[str],
|
|
300
|
-
selectors: list[str],
|
|
301
|
-
) -> str:
|
|
302
|
-
if not positionals:
|
|
303
|
-
return format_error("import requires SYMBOL.", None)
|
|
304
|
-
symbol_name = positionals[0]
|
|
305
|
-
|
|
306
|
-
parsed_selectors = [s for s in (parse_selector(sel) for sel in selectors) if s is not None]
|
|
307
|
-
|
|
308
|
-
file_sel = next((s for s in parsed_selectors if s.selector_type == SelectorType.FILE), None)
|
|
309
|
-
line_sel = next((s for s in parsed_selectors if s.selector_type == SelectorType.LINE), None)
|
|
310
|
-
|
|
311
|
-
if file_sel is None:
|
|
312
|
-
return format_error("import requires @file:PATH selector.", None)
|
|
313
|
-
if line_sel is None:
|
|
314
|
-
return format_error("import requires @line:N selector.", None)
|
|
315
|
-
|
|
316
|
-
try:
|
|
317
|
-
line_num = int(line_sel.value)
|
|
318
|
-
except ValueError:
|
|
319
|
-
return format_error("invalid line number.", None)
|
|
320
|
-
|
|
321
|
-
uri = file_uri(model, file_sel.value)
|
|
322
|
-
lsp_line = line_num - 1 if line_num > 0 else line_num
|
|
323
|
-
|
|
324
|
-
client = model.lsp_client
|
|
325
|
-
assert client is not None
|
|
326
|
-
|
|
327
|
-
try:
|
|
328
|
-
await ensure_file_synced(model, uri)
|
|
329
|
-
except Exception as e:
|
|
330
|
-
return format_error(f"import: {e}", None)
|
|
331
|
-
|
|
332
|
-
params = {
|
|
333
|
-
"textDocument": {"uri": uri},
|
|
334
|
-
"range": {
|
|
335
|
-
"start": {"line": lsp_line, "character": 0},
|
|
336
|
-
"end": {"line": lsp_line, "character": 999},
|
|
337
|
-
},
|
|
338
|
-
"context": {
|
|
339
|
-
"diagnostics": [],
|
|
340
|
-
"only": ["quickfix", "source", "source.organizeImports"],
|
|
341
|
-
"triggerKind": 1,
|
|
342
|
-
},
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
try:
|
|
346
|
-
raw_actions = await client.request("textDocument/codeAction", params)
|
|
347
|
-
except Exception as e:
|
|
348
|
-
return format_error(f"import failed: {e}", None)
|
|
349
|
-
|
|
350
|
-
if not raw_actions:
|
|
351
|
-
raw_actions = []
|
|
352
|
-
|
|
353
|
-
actions = [CodeAction.from_dict(a) for a in raw_actions]
|
|
354
|
-
symbol_lower = symbol_name.lower()
|
|
355
|
-
|
|
356
|
-
import_actions = [
|
|
357
|
-
a for a in actions
|
|
358
|
-
if (
|
|
359
|
-
(a.kind and (
|
|
360
|
-
"import" in a.kind
|
|
361
|
-
or a.kind == "quickfix"
|
|
362
|
-
or a.kind.startswith("source")
|
|
363
|
-
))
|
|
364
|
-
or "import" in a.title.lower()
|
|
365
|
-
or "use " in a.title.lower()
|
|
366
|
-
)
|
|
367
|
-
and symbol_lower in a.title.lower()
|
|
368
|
-
]
|
|
369
|
-
|
|
370
|
-
if not import_actions:
|
|
371
|
-
return format_error(
|
|
372
|
-
f"no import action for '{symbol_name}' at {file_sel.value}:{line_num}.",
|
|
373
|
-
None,
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
if len(import_actions) == 1:
|
|
377
|
-
action = import_actions[0]
|
|
378
|
-
else:
|
|
379
|
-
preferred = next((a for a in import_actions if a.is_preferred), None)
|
|
380
|
-
if preferred:
|
|
381
|
-
action = preferred
|
|
382
|
-
else:
|
|
383
|
-
return format_code_action_choices(import_actions)
|
|
384
|
-
|
|
385
|
-
if action.edit is None:
|
|
386
|
-
return format_error("import action has no edit.", None)
|
|
387
|
-
|
|
388
|
-
try:
|
|
389
|
-
result = apply_workspace_edit(action.edit)
|
|
390
|
-
except Exception as e:
|
|
391
|
-
return format_error(f"failed to apply import: {e}", None)
|
|
392
|
-
|
|
393
|
-
return format_mutation_result("import", symbol_name, result, model.root_uri)
|
fcp_python/domain/verbs.py
CHANGED
|
@@ -24,8 +24,6 @@ def register_query_verbs(registry: VerbRegistry) -> None:
|
|
|
24
24
|
def register_mutation_verbs(registry: VerbRegistry) -> None:
|
|
25
25
|
registry.register_many([
|
|
26
26
|
VerbSpec(verb="rename", syntax="rename SYMBOL NEW_NAME [@selectors...]", category="mutation"),
|
|
27
|
-
VerbSpec(verb="extract", syntax="extract FUNC_NAME @file:PATH @lines:N-M", category="mutation"),
|
|
28
|
-
VerbSpec(verb="import", syntax="import SYMBOL @file:PATH @line:N", category="mutation"),
|
|
29
27
|
])
|
|
30
28
|
|
|
31
29
|
|
fcp_python/main.py
CHANGED
|
@@ -55,9 +55,7 @@ _registry = _make_registry()
|
|
|
55
55
|
@mcp.tool(
|
|
56
56
|
description=(
|
|
57
57
|
"Execute Python mutation operations. Examples: "
|
|
58
|
-
"'rename Config Settings'
|
|
59
|
-
"'extract validate @file:server.py @lines:15-30', "
|
|
60
|
-
"'import os @file:main.py @line:5'"
|
|
58
|
+
"'rename Config Settings'"
|
|
61
59
|
)
|
|
62
60
|
)
|
|
63
61
|
async def python(ops: list[str]) -> str:
|
|
@@ -108,13 +106,10 @@ async def python_help() -> str:
|
|
|
108
106
|
" @kind:KIND — filter by symbol kind (function, class, method, variable, ...)\n"
|
|
109
107
|
" @module:NAME — filter by module\n"
|
|
110
108
|
" @line:N — filter by line number\n"
|
|
111
|
-
" @lines:N-M — line range for extract\n"
|
|
112
109
|
" @decorator:NAME — filter by decorator"
|
|
113
110
|
),
|
|
114
111
|
"Mutation Examples": (
|
|
115
|
-
' python ["rename Config Settings"] — cross-file semantic rename
|
|
116
|
-
' python ["extract validate @file:server.py @lines:15-30"] — extract function\n'
|
|
117
|
-
' python ["import os @file:main.py @line:5"] — add missing import'
|
|
112
|
+
' python ["rename Config Settings"] — cross-file semantic rename'
|
|
118
113
|
),
|
|
119
114
|
}
|
|
120
115
|
return _registry.generate_reference_card(extra)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fcp-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python Code Intelligence FCP — semantic Python code operations for LLMs via pylsp
|
|
5
5
|
Requires-Python: <3.14,>=3.11
|
|
6
6
|
Requires-Dist: fastmcp>=3.0
|
|
7
|
-
Requires-Dist: fcp-core>=0.
|
|
7
|
+
Requires-Dist: fcp-core>=0.2.0
|
|
8
8
|
Requires-Dist: python-lsp-server[rope]>=1.12
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
fcp_python/__init__.py,sha256=jXbt2O90IFhcdC8JQ4qr2zYFSlz1Zvpml9hEam5JstU,58
|
|
2
|
-
fcp_python/bridge.py,sha256=
|
|
3
|
-
fcp_python/main.py,sha256=
|
|
2
|
+
fcp_python/bridge.py,sha256=2hiVRmJJotgCZFVRvZ9BZf3X4rLo5-ILyXmbxNL1dbA,5737
|
|
3
|
+
fcp_python/main.py,sha256=aUQ50PTUCWvW30AnD6TkYo2n69flryVCyE1MHRtwvVc,9116
|
|
4
4
|
fcp_python/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
fcp_python/domain/format.py,sha256=9oHtn3d9vvRT28DrJxNs9m-YpIWjfjIr-pMWF5Tu4v0,7311
|
|
6
6
|
fcp_python/domain/model.py,sha256=yI2kVvEWv50wICqqmrHV_302EVMtZd_t1v1X50ZonO8,1458
|
|
7
|
-
fcp_python/domain/mutation.py,sha256=
|
|
7
|
+
fcp_python/domain/mutation.py,sha256=ZiTRZdZI0zqqFpFZ-v8CWtNzpMO8L9tcsV_yMipqM1A,2987
|
|
8
8
|
fcp_python/domain/query.py,sha256=KFve1HFAKwyHJPgT1INEQ97jieUUDvhswho-NHqrxkc,21706
|
|
9
|
-
fcp_python/domain/verbs.py,sha256
|
|
9
|
+
fcp_python/domain/verbs.py,sha256=-w5zY1BPJmVCOC1a-0xd3uj5EAjpNN4UeY-e51LRLbI,1717
|
|
10
10
|
fcp_python/lsp/__init__.py,sha256=Kai6_M5ap1qjrLEfpkdcVZenIzQDQHMUu28JscQg_kM,39
|
|
11
11
|
fcp_python/lsp/client.py,sha256=H-I1Hq8ayAUimGcsMpiLmFZcdYHRvQBL0jYG--F51JU,6428
|
|
12
12
|
fcp_python/lsp/lifecycle.py,sha256=i-b61LGOeQxZIUjUAf_oR8nMqS7rnGR52EYb9KM3zHc,2774
|
|
@@ -17,7 +17,7 @@ fcp_python/resolver/__init__.py,sha256=9ImXXfUvA0TWGDZx1GMniKzav7SKT9q9u1C_0EOZE
|
|
|
17
17
|
fcp_python/resolver/index.py,sha256=9y56ZoBNqwl8a4_Ue54ZBjNWzj3kZ548miIZvab_90k,1859
|
|
18
18
|
fcp_python/resolver/pipeline.py,sha256=cPqawRgQ3hxNKottcsco1RGLT4f2c3IWidqnuSzhh7c,3174
|
|
19
19
|
fcp_python/resolver/selectors.py,sha256=u_NQdp6eU2fPSKnSpWNhTZbxwy6OgvxVTR3pKdB_7Rg,5070
|
|
20
|
-
fcp_python-0.
|
|
21
|
-
fcp_python-0.
|
|
22
|
-
fcp_python-0.
|
|
23
|
-
fcp_python-0.
|
|
20
|
+
fcp_python-0.2.0.dist-info/METADATA,sha256=jEWXwfiHLz2iljasXFEDgbUPpPTP7cr6foGcA4UUxuI,281
|
|
21
|
+
fcp_python-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
22
|
+
fcp_python-0.2.0.dist-info/entry_points.txt,sha256=iSyoGO8uLz21KTX8mL3twnRXWsuc7-8O3U03WCSiJ_s,52
|
|
23
|
+
fcp_python-0.2.0.dist-info/RECORD,,
|
|
File without changes
|