Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 15 additions & 22 deletions polyapi/deployables.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,29 +191,27 @@ def write_cache_revision(git_revision: Optional[str] = None) -> None:
with open(CACHE_VERSION_FILE, 'w', encoding='utf-8') as file:
file.write(git_revision)


def is_cache_up_to_date() -> bool:
"""Check if the cached revision matches the current Git revision."""
if not Path(CACHE_VERSION_FILE).exists():
return False
with open(CACHE_VERSION_FILE, 'r', encoding='utf-8') as file:
cached_revision = file.read().strip()
git_revision = get_git_revision()
return cached_revision == git_revision

def is_cache_up_to_date() -> bool:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow. 2 versions of the same function! 🤦 I think just deleting the second one here and keeping the first one as is would be a good change. We still want to check the cached_revision file rather than use git status --porcelain simply because there could be changes already committed into the repo which have not been picked up or prepared.

"""Check if the cached revision matches the current Git revision."""
cached_revision = get_cache_deployments_revision()
git_revision = get_git_revision() # This function needs to be defined or imported
return cached_revision == git_revision

def write_deploy_comments(deployments: List[Dict]) -> str:
"""Generate a string of deployment comments for each deployment."""
def write_deploy_comments(deployments: list) -> str:
"""
Generate deployment comments for each deployment record.
"""
canopy_path = 'polyui/collections' if 'localhost' in os.getenv('POLY_API_BASE_URL', '') else 'canopy/polyui/collections'
comments = []
for d in deployments:
instance_url = d['instance'].replace(':8000', ':3000') if d['instance'].endswith(':8000') else d['instance']
comment = f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}"
comments.append(comment)
return '\n'.join(comments)
comments.append(f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}")
return "\n".join(comments)

def print_docstring_function_comment(description: str, args: list, returns: dict) -> str:
docstring = f'"""{description}\n\n'
Expand Down Expand Up @@ -247,8 +245,13 @@ def update_deployment_comments(file_content: str, deployable: dict) -> str:
file_content = file_content[:range[0]] + file_content[range[1]:]
if deployable['deployments']:
deployment_comments = write_deploy_comments(deployable['deployments'])
deployable['deploymentCommentRanges'] = [(0, len(deployment_comments) + 1)]
file_content = f"{deployment_comments}\n{file_content}"
# Add blank line after deployment comments only if file content doesn't start with blank line
if file_content.startswith('\n'):
deployable['deploymentCommentRanges'] = [(0, len(deployment_comments) + 1)]
file_content = f"{deployment_comments}\n{file_content}"
else:
deployable['deploymentCommentRanges'] = [(0, len(deployment_comments) + 2)]
file_content = f"{deployment_comments}\n\n{file_content}"
Comment on lines +248 to +254
Copy link
Member

@aarongoin aarongoin Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives me pause--not because the change or code is bad--but simply because I don't see how this could be fixing the bug Eric is encountering given this was already adding a newline in place, right?

So how did Eric get this outcome:
image

Looking at his picture again it seems like the deploy comment is getting formatted across multiple lines which seems odd. Nothing in here would be doing that as far as I know... Wonder if Eric has some auto-linting or formatting coming into play here. Might be worth circling back to Eric to see if he can show you his git diffs on this.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, @eneumann is this happening consistently/is it reproducible? Also having git diffs would be helpful to see if the deployment process is changing the file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ash1R yep it's consistent, here is a fresh repro on a fresh py glide project:

initial CFX

from polyapi.typedefs import PolyClientFunction

polyConfig: PolyClientFunction = {
    "name": "cfx",
    "context": "foo",
}

def cfx(foo: str) -> str:
    """blah.

    Args:
        foo (str): The input string to be processed.

    Returns:
        str: A string indicating the received input.
    """
    return f"Received foo: {foo}"

After git push:
image
image

From poly deploy action:

Preparing Poly deployments...
Searching for poly deployables.
Found 1 possible deployable file.
Prepared server function foo.cfx: OK
Poly deployments are prepared.
Cached deployables and generated typedefs into polyapi/cached_deployables directory.
Synced server function foo.cfx: ADDED
Synced server function demo.hello_poly: NOT FOUND
Poly deployments synced.

After git pull:

# Poly deployed @ 2025-07-25T00:23:22.387449 - foo.cfx - https://eu1.polyapi.io/canopy/polyui/collections/server-functions/e72cf2cd-26a7-4824-8a91-546999cb2813 - b2b9ed1from polyapi.typedefs import PolyClientFunction

polyConfig: PolyClientFunction = {
    "name": "cfx",
    "context": "foo",
}

def cfx(foo: str) -> str:
    """blah.

    Args:
        foo (str): The input string to be processed.

    Returns:
        str: A string indicating the received input.
    """
    return f"Received foo: {foo}"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eneumann this is a long shot, but what version of the client are you using?

I dimly remember a certain recent version of the python client where EVERYTHING was deployed as a server function due to a bug in the code.

I couldn't really repro this with python -m polyapi sync and a fresh-ish env

if you are on the latest version though, i will try to repro again with a true fresh version

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eupharis I'm on v 0.3.9.dev13

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Could be an issue where the outer-most type of "client-function" is getting clobbered by the deployments type of "server-function"? Is it possible this function existed in eu1 before you deployed via glide? Like maybe it was a server function first and then we switched over, but when we deploy it doesn't change the type and it stays a server function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aarongoin no it was a fresh new tenant and fresh new glide project. I just pushed a new CFX and it again deployed as a SFX, but the cache has an empty deployments list.

image
{
  "context": "fresh",
  "name": "freshNewCfx",
  "description": "blah.",
  "config": {
    "name": "freshNewCfx",
    "context": "fresh"
  },
  "gitRevision": "55a7a95",
  "fileRevision": "e85101d",
  "file": "./src/clientFunctions/freshNewCfx.py",
  "types": {
    "description": "blah.",
    "params": [
      {
        "name": "foo",
        "type": "str",
        "description": "The input string to be processed.",
        "typeSchema": null
      }
    ],
    "returns": {
      "description": "A string indicating the received input.",
      "type": "str",
      "typeSchema": null
    },
    "raises": {}
  },
  "typeSchemas": {},
  "dependencies": [],
  "deployments": [],
  "deploymentCommentRanges": [],
  "docStartIndex": 178,
  "docEndIndex": 325,
  "dirty": false,
  "type": "client-function",
  "disableAi": false
}

Copy link
Contributor Author

@Ash1R Ash1R Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw a potential fix in enforcing the new type while updating in sync, but if it was new then the bug is something else?
Right now, I can't reproduce the issue, @eneumann is it still happening, and is it only an eu1 issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ash1R interestingly enough, it is only happening on EU1. Works as expected on DEV, NA2, and NA1. I used the same glide repo and thus the same CFXs and SFXx across all instances and it worked fine everywhere except EU1. I also switched to a different tenant on EU1 and it still did not work. So it can be consistently repro'd on EU1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eneumann fascinating! great find!

return file_content

def update_deployable_function_comments(file_content: str, deployable: dict, disable_docs: bool = False) -> str:
Expand Down Expand Up @@ -288,13 +291,3 @@ def write_updated_deployable(deployable: dict, disable_docs: bool = False) -> di
deployable['fileRevision'] = get_deployable_file_revision(file_contents)
return deployable

def write_deploy_comments(deployments: list) -> str:
"""
Generate deployment comments for each deployment record.
"""
canopy_path = 'polyui/collections' if 'localhost' in os.getenv('POLY_API_BASE_URL', '') else 'canopy/polyui/collections'
comments = []
for d in deployments:
instance_url = d['instance'].replace(':8000', ':3000') if d['instance'].endswith(':8000') else d['instance']
comments.append(f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}")
return "\n".join(comments)
Comment on lines -291 to -300
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't just delete this. These comments are an important part of glide, and this function is still being called up in update_deployment_comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a duplicate function, there is still a function with this name in the file I think

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh weird! Nice catch. 👍 Let's diff them to make sure they're the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They seem the same except the comment for function description
Screenshot 2025-07-23 at 10 24 19 AM
Uploading Screenshot 2025-07-23 at 10.24.50 AM.png…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They seem the same except the comment for function description
Screenshot 2025-07-23 at 10 24 19 AM
Screenshot 2025-07-23 at 10 24 50 AM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can live with that! Delete away! 😀

5 changes: 3 additions & 2 deletions polyapi/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
)

DEPLOY_ORDER = [
'server-function',
'client-function',
'server-function',
]

def read_file(file_path: str) -> str:
Expand Down Expand Up @@ -129,7 +129,8 @@ def sync_deployables(dry_run: bool, instance: str | None = None):
**deployable,
**previous_deployment,
"description": deployable["types"]["description"],
"instance": instance
"instance": instance,
"type": deployable["type"]
}
else:
sync_deployment = { **deployable, "instance": instance }
Expand Down
66 changes: 65 additions & 1 deletion tests/test_deployables.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,68 @@ def test_parse_and_write_deployable_docstring(self):
def test_parse_and_overwrite_docstring(self):
parsed_deployable = parse_function_code(EXPECTED_SERVER_FN_DOCSTRINGS)
updated_file_contents = update_deployable_function_comments(EXPECTED_SERVER_FN_DOCSTRINGS, parsed_deployable)
self.assertEqual(EXPECTED_SERVER_FN_DOCSTRINGS, updated_file_contents)
self.assertEqual(EXPECTED_SERVER_FN_DOCSTRINGS, updated_file_contents)

def test_deployment_comments_with_imports_at_top(self):
"""Test that deployment comments are placed at the very top, before import statements."""
# File content that starts with imports (no existing deployment comments)
file_with_imports = '''from typing import Dict
from polyapi.typedefs import PolyServerFunction

polyConfig: PolyServerFunction = {
"name": "foobar",
"context": "testing",
"logsEnabled": True,
}

def foobar(foo: str, bar: Dict[str, str]) -> int:
"""A function that does something really important."""
print("Okay then!")
return 7
'''

expected_with_deployment_comments = '''# Poly deployed @ 2024-11-12T14:43:22.631113 - testing.foobar - https://na1.polyapi.io/canopy/polyui/collections/server-functions/jh23h5g3h5b24jh5b2j3h45v2jhg43v52j3h - 086aedd

from typing import Dict
from polyapi.typedefs import PolyServerFunction

polyConfig: PolyServerFunction = {
"name": "foobar",
"context": "testing",
"logsEnabled": True,
}

def foobar(foo: str, bar: Dict[str, str]) -> int:
"""A function that does something really important."""
print("Okay then!")
return 7
'''

test_deployable = parse_function_code(file_with_imports, "foobar")

# Add a mock deployment to test comment placement
test_deployable["deployments"] = [{
'context': 'testing',
'deployed': '2024-11-12T14:43:22.631113',
'fileRevision': '086aedd',
'id': 'jh23h5g3h5b24jh5b2j3h45v2jhg43v52j3h',
'instance': 'https://na1.polyapi.io',
'name': 'foobar',
'type': 'server-function'
}]

# Update deployment comments this should place comments at the very top
updated_file_contents = update_deployment_comments(file_with_imports, test_deployable)

self.assertEqual(updated_file_contents, expected_with_deployment_comments)

# ensure the first line is a deployment comment, not an import
first_line = updated_file_contents.split('\n')[0]
self.assertTrue(first_line.startswith('# Poly deployed @'),
f"Expected deployment comment at top, but first line was: {first_line}")

# Ensure imports come after the deployment comment and blank line
lines = updated_file_contents.split('\n')
import_line_index = next(i for i, line in enumerate(lines) if line.startswith('from typing import'))
self.assertGreater(import_line_index, 1,
"Import statements should come after deployment comments and blank line")
Comment on lines +179 to +188
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything you're testing here is already covered by self.assertEqual(updated_file_contents, expected_with_deployment_comments) right?